您的位置:首页 > 其它

HDU 5304 Eastest Magical Day Seep Group's Summer(状压DP+环缩点+生成树计数)

2015-11-11 19:41 260 查看
题意:给出一个图m条边和n个顶点,要求删除m-n条边使得原图联通,问有多少种方法。

思路:显然删除后的图是由一棵树和额外的一条边组成的,如果只有树那么可以用MATRIX-TREE定理求出生成树的数量,但是现在多了一条边。

考虑多了的这条边,这条边使得树上形成了一条环,那么我们只需要将这个环缩成一个点然后用生成树计数就可以解决了。

注意到,这道题对于经过点相同的环是等价的,考虑用状压dp来求出经过顶点状态为i的环的个数,

具体的来说,用dp[i][j]表示经过顶点集合为i且以i中最小编号为起点,以j顶点为终点的路径个数,然后对于每个状态判断起点和终点是否有边相连,如果有的话,就加当前dp[i][j]加到环的个数ans[i]上,这一步的时间复杂度为O(n^2*2^n),然后枚举所有环,对于这个环缩点然后MATRIX-TREE定理求出生成树的数量,这一步的时间复杂度为O(n^3*2^n)。

#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<vector>
#include<map>
#include<queue>
#include<stack>
#include<string>
#include<map>
#include<set>
#include<ctime>
#define eps 1e-6
#define LL long long
#define pii pair<int, int>
//#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;

//const int MAXN = 5000000 + 5;
//const int INF = 0x3f3f3f3f;
const int MOD = 998244353;
int n, m;
int G[20][20];
LL dp[(1<<16)+5][17], ans[(1<<16)+5];
void exgcd(int a, int b, int &x, int &y) {
if (b) {
exgcd(b, a % b, y, x);
y -= a / b * x ;
}
else {
x = 1;
y = 0;
}
}
int inv(int a){
int x, y, b = MOD;
exgcd(a, b, x, y);
if (x < 0) x += MOD;
return x;
}

struct Matrix {
LL a[20][20];
Matrix() {
memset(a, 0, sizeof(a));
}
void clear() {
memset(a, 0, sizeof(a));
}
LL det(int n)//求行列式的值模上MOD,需要使用逆元
{
LL res = 1;
for(int i = 0;i < n;i++)
{
for(int j = i;j < n;j++)
if(a[j][i]!=0)
{
for(int k = i;k < n;k++)
swap(a[i][k],a[j][k]);
if(i != j)
res = (-res+MOD)%MOD;
break;
}
if(a[i][i] == 0)
{
res = 0;//不存在(也就是行列式值为0)
break;
}
for(int j = i+1;j < n;j++)
{
//int mut = (a[j][i]*INV[a[i][i]])%MOD;//打表逆元
LL mut = (a[j][i]*inv((int)a[i][i]))%MOD;
for(int k = i;k < n;k++) {
a[j][k] = (a[j][k]-(a[i][k]*mut))%MOD;
if(a[j][k] < 0 ) a[j][k] += MOD;
}
}
res = (res * a[i][i])%MOD;
}
return res;
}
};
int tot, Hash[20];
int main() {
//freopen("input.txt", "r", stdin);
while(cin >> n >> m) {
memset(dp, 0, sizeof(dp));
memset(G, 0, sizeof(G));
memset(ans, 0, sizeof(ans));
for(int i = 1, u, v; i <= m; i++) {
scanf("%d%d", &u, &v);
u--; v--;
G[u][v] = G[v][u] = 1;
}
int maxs = (1<<n) - 1;
for(int i = 1; i <= maxs; i++) {
int st = __builtin_ffs(i) - 1;
int beg = st + 1;
if((1<<st) == i) {
dp[i][st] = 1;
ans[i] = 1;
beg = st;
}
for(int j = beg; j < n; j++) if(((1<<j)&i)) {
if(G[st][j]) ans[i] = (ans[i] + dp[i][j]) % MOD;
for(int k = st+1; k < n; k++) if(G[j][k] && !((1<<k)&i)) {
dp[i^(1<<k)][k] = (dp[i^(1<<k)][k] + dp[i][j]) % MOD;
}
}
}
LL shit = 0;
for(int i = 1; i <= maxs; i++) {
if(__builtin_popcount(i)<3 || !ans[i]) continue;
ans[i] = ans[i] * inv(2) % MOD;
tot = 0;
Matrix tmp;
memset(Hash, -1, sizeof(Hash));
for(int j = 0; j < n; j++)
if((1<<j)&i) Hash[j] = 0;
for(int j = 0; j < n; j++) {
if(Hash[j]<0) Hash[j] = ++tot;
for(int k = j+1; k < n; k++) {
if(Hash[k]<0) Hash[k] = ++tot;
int t1 = Hash[j], t2 = Hash[k];
if(t1 != t2) {
//if(i==13) cout << j << " " << Hash(j) << " " << k << " " << Hash(k) << endl;
tmp.a[t1][t2] -= G[k][j];
tmp.a[t2][t1] -= G[k][j];
tmp.a[t1][t1] += G[k][j];
tmp.a[t2][t2] += G[k][j];
}
}
}
/*if(i==13) {
cout << tot << endl;
for(int j = 0; j <= tot; j++)
for(int k = 0; k <= tot; k++) cout << j << " " << k << " " << tmp.a[j][k] << endl;
}*/
shit = (shit + ans[i]*tmp.det(tot)) % MOD;
//cout << i << " " << ans[i] << " " << tmp.det(tot) << endl;
}
cout << shit << endl;
//for(int i = 1; i <= maxs; i++) cout << i << " " << ans[i] << endl;
//cout << ans << endl;
}
return 0;
}




内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息