●BZOJ 2560 串珠子

题链:

http://www.lydsy.com/JudgeOnline/problem.php?id=2560

题解:

容斥,状压计数dp
首先求出一个数组 g[s] 表示集合内的点的连边方案数(两个点间包括不连边和连边)
即           n
    g[s]=  ∏ (c[i][j]+1)
            i,j∈s
然后定义一个 f[s] 表示 s的集合构成联通图的方案数
直接f[s]=g[s]么?显然不是,因为 g[s]里还包含有不联通的情况。
所以需要减去一些东西。 一下的操作就比较妙了。
考虑把 s集合里的最小元素 A 固定,使得集合内的所有点都要在 A 的联通图里。
然后枚举 s 的所有子集 _s,
若 A不在 _s里,则表明 A所在的联通图 s - _s 与 _s 没有联通,则这是一类非法方案,
即 f[s]-=f[s-_s]*g[_s]。
最后答案就是 f[全集]。

代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#define _ %mod
#define filein(x) freopen(#x".in","r",stdin);
#define fileout(x) freopen(#x".out","w",stdout);
using namespace std;
const int mod=1000000007;
int c[25][25],f[(1<<16)+10],g[(1<<16)+10];
int n;
int main()
{
	scanf("%d",&n);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            scanf("%d",&c[i][j]);
	for(int s=1;s<1<<n;s++){
		g[s]=1;
		for(int i=0;i<n;i++) if((1<<i)&s)
			for(int j=i;j<n;j++) if((1<<j)&s)
				g[s]=1ll*g[s]*(c[i+1][j+1]+1)_;
	}
	for(int s=1;s<1<<n;s++){
		f[s]=g[s];
		int ii=s&-s;
		for(int _s=s;_s;_s=(_s-1)&s) if(~_s&ii)
			f[s]=((f[s]-1ll*f[s-_s]*g[_s]_+mod)_)_;
	}
	printf("%d",f[(1<<n)-1]);
	return 0;
}


 

posted @ 2017-12-12 19:37  *ZJ  阅读(152)  评论(0编辑  收藏  举报