朝暮(枚举基准 容斥)

朝暮(枚举基准 容斥)

给出一个n个点,最多n+6条边的无向图,求将树黑白染色,且没有相邻黑点的方案数。\(1\leqslant n\leqslant 10^5\)

首先,如果这个图是个树,就是个斯波dp。

然后我在考试时想了个做法:枚举所有非树边旁边是00还是10还是01。复杂度\(n3^n\),成功tle。

正解就好写得多。枚举旁边都是黑点的非树边有几条,容斥一下即可。

我其实还没搞懂为什么还能这样容斥……果然自己计数题太弱了。

注意\((-1)^x\)到底是啥!被坑了好久。

#include <cstdio> 
#include <cstring>
using namespace std;

const int maxn=1e5+10, mod=1e9+7;
int n, m;
struct Edge{
	int to, nxt;
}e[maxn*2];
int fir[maxn], cnte;
void addedge(int x, int y){
	Edge &ed=e[++cnte];
	ed.to=y; ed.nxt=fir[x]; fir[x]=cnte;
}

inline int popcount(int x){ int re=0;
	while (x) re+=x&1, x>>=1;
	return re; }

int qd[maxn], ex[6], ey[maxn], cntext;
long long f[maxn][2], ans;

int fa[maxn];  //有向图求返祖边才需要dfs!无向图只需要并查集 
int find(int x){ return fa[x]==x?x:fa[x]=find(fa[x]); }

void dfs(int now, int par){
	f[now][0]=f[now][1]=1;
	if (qd[now]) f[now][0]=0;
	for (int i=fir[now]; i; i=e[i].nxt){
		if (e[i].to==par) continue;
		dfs(e[i].to, now);
		(f[now][0]*=(f[e[i].to][0]+f[e[i].to][1]))%=mod;
		(f[now][1]*=f[e[i].to][0])%=mod;
	}
}

int main(){
	//freopen("20.in", "r", stdin); freopen("20.out", "w", stdout);
	scanf("%d%d", &n, &m); int x, y;
	for (int i=1; i<=n; ++i) fa[i]=i;
	for (int i=1; i<=m; ++i){
		scanf("%d%d", &x, &y);
		if (find(x)==find(y)){ 
			ex[cntext]=x; ey[cntext++]=y; 
			continue;
		}
		addedge(x, y); addedge(y, x);
		fa[find(x)]=find(y);
	}
	for (int i=0; i<(1<<cntext); ++i){
		memset(f, 0, sizeof(f));
		memset(qd, 0, sizeof(qd));
		for (int j=0; j<cntext; ++j)
			if (i&(1<<j)) qd[ex[j]]=qd[ey[j]]=1;
		dfs(1, 0);
		//要判断的是popcount的奇偶性! 
		(ans+=(popcount(i)&1?-1:1)*(f[1][0]+f[1][1]))%=mod;
	}
	printf("%lld\n", (ans+mod)%mod);
	return 0;
}
posted @ 2018-08-11 15:23  pechpo  阅读(138)  评论(0编辑  收藏  举报