CF1221G - Graph And Numbers

CF1221G - Graph And Numbers

题目大意

给定一个\(n\)\(m\)边的无向图,\(n\leq 40\)

求给所有点01染色,满足

至少存在一条边两边的点均为0

至少存在一条边两边的点一个为0,一个为1

至少存在一条边两边的点均为1

的方案数


分析

至少存在 问题并不好处理,由于限制有3个,可以通过\(2^3\)种情况容斥得到

设三种边类型为0,1,2

即计算

1.不存在0

2.不存在1

3.不存在2

4.不存在01

5.不存在02

6.不存在12

7.不存在012

逐个击破

2.即计算所有边连接两个点染色相同方案数,统计连通块即可

4/6即统计所有点两边都是1/0的方案数

5即统计所有边两端点颜色不同的方案数,即二分图染色数

7.即m=0

1,3类似,可以归纳为每条边两端的点至少有一个为1

似乎有点类似一般图独立集个数的求解

由于\(n\leq 40\),考虑 \(\text{meet in the middle}\)

枚举半边,判断集合内部是否有非法边,然后根据集合之间的非法边以及自己集合内部为0的点

确定另一个集合必须选择为1的点集

因此需要一个父集前缀和

const int N=45;

int n,m;
int G[N][N];
ll E[N]; // 这东西居然要开long long

ll Solve0(){
	static int S[1<<20];
	ll ans=0;
	int m=n/2,A=(1<<m)-1;
	rep(i,0,(1<<m)-1) {
		ll T=0;
		rep(j,0,m-1) if(~i&(1<<j)) T|=E[j];
		S[i]=(~i&T&A)==0;
	}
    // 父集前缀和
	for(int i=1;i<=A;i<<=1) for(int l=0;l<=A;l+=i*2) for(int j=l;j<l+i;++j) S[j]+=S[j+i];
	rep(i,0,(1<<(n-m))-1) {
		ll T=0;
		rep(j,0,n-m-1) if(~i&(1<<j)) T|=E[j+m];
		if((T>>m)&~i) continue;
		T&=A,ans+=S[T];
	}
	return ans;
}

ll Solve1(){
	static int vis[N];
	function<void(int)> dfs=[&](int u) {
		if(vis[u]) return;
		vis[u]=1;
		rep(i,0,n-1) if(G[u][i]) dfs(i);
	};
	ll ans=1;
	rep(i,0,n-1) if(!vis[i]) dfs(i),ans<<=1;
	return ans;
}

ll Solve01(){
	ll ans=1;
	rep(i,0,n-1) if(!E[i]) ans<<=1;
	return ans;
}

ll Solve02(){
	static int vis[N],fl=1;
	function <void(int,int)> dfs=[&](int u,int c) {
		if(vis[u]) {
			if(vis[u]!=c) fl=0;
			return;
		}
		vis[u]=c;
		rep(i,0,n-1) if(G[u][i]) dfs(i,3-c);
	};

	ll ans=1;
	rep(i,0,n-1) if(!vis[i]) dfs(i,1),ans<<=1;
	return fl*ans;
}


int main(){
	n=rd(),m=rd();
	rep(i,1,m) {
		int x=rd()-1,y=rd()-1;
		G[x][y]=G[y][x]=1;
		E[x]|=1ll<<y,E[y]|=1ll<<x;
	}
	ll ans=1ll<<n;
	ans-=2*Solve0(),ans-=Solve1();
	ans+=2*Solve01()+Solve02();
	if(m==0) ans-=1ll<<n;
	printf("%lld\n",ans);
}
posted @ 2021-05-08 17:19  chasedeath  阅读(59)  评论(0编辑  收藏  举报