Loading

CF1221G 题解

特判 \(m=0\)。然后考虑做一个容斥。令 \(F(E)\;(E\subseteq\{0,1,2\})\) 表示边权集合 \(\subseteq E\)(没有不在 \(E\) 内的数字)的方案数。那么答案为

\[F(\{0,1,2\})-F(\{0,1\})-F(\{1,2\})-F(\{0,2\})+F(\{0\})+F(\{1\})+F(\{2\}) \]

对原图求孤点(所在连通块大小为 \(1\))个数 \(c\) 与连通块个数 \(k\)(另一种做法是把孤点全部删掉再处理),显然有:

  • \(F(\{0,1,2\})=2^n\)
  • \(F(\{0\})=F(\{2\})=2^c\)。这等价于,对于大小 \(>1\) 的所有连通块包含的点,它们的点权必须全为 \(0\),或者全为 \(1\)
  • \(F(\{0,2\})=2^k\)。每个连通块要么全为 \(0\),要么全为 \(1\)

以及:

  • 若原图不是二分图,那么 \(F(\{1\})=0\)。对于每条边,它两端点的点权必须恰为 \(1\)\(0\)。这就是黑白染色,也就是二分图的模型。
  • 否则,\(F(\{1\})=2^k\)。每个连通块有两种染色方案。

现在还剩下 \(F(\{0,1\})\)\(F(\{1,2\})\)。它们意即,原图中没有相邻的 \(1\),或是没有相邻的 \(0\)。那么有 \(F(\{0,1\})=F(\{1,2\})\)

问题等价于,在原图中选出一个独立集,求方案数。

\(n=40\),且考场有 \(n=20\) 的包,启发我们折半搜索。取阈值 \(B\),对编号 \(\le B\) 的结点,枚举选择它们的所有合法方案 \(S\),记 \(S\) 中所有点的邻接点集为 \(A\),那么编号 \(>B\) 的结点,可选择的范围就是 \(T=\large \complement_{[1,n]}A\)。即,编号 \(>B\) 结点选择的合法方案,一定是 \(T\) 的子集。

这是一个明显的 SOS(Sum Over Subsets)计数。使用 FWT 等技巧可以在 \(\mathcal O(2^{n-B})\) 内完成。可我只会 \(\Theta(3^{n-B})\) 对每种可能的 \(T\subseteq (B,n]\) 暴力枚举子集啊。分析复杂度:

\[\Theta(2^B+3^{n-B}) \]

\(2^B=3^{n-B}\),解得 \(B=n\times \log_6 3\)。那么总复杂度就是 \(\Theta(2^B)\)\(n=40\)\(B\approx 24.525\),取 \(B=24\)。完全不卡。

精细实现(预处理 \(A\) 等)可以让搜索过程的复杂度完全不带 \(n\)\(m\)

#include <cstdio>
#include <cmath>
#define ll long long

double log2(double x){
	return log(x)/log(2);
}

const int N=114514;

int F,S,A,B,adj[50],sad[50];

ll sos[N],ans;

void whl(int now){
	if(now>B) return ans+=sos[F^S],void();
	if(!(A&adj[now])){
		int ps=S,pa=A;S|=sad[now];A|=(1<<now-1);
		whl(now+1);S=ps;A=pa;
	}
	whl(now+1);
}

int n;bool bass[N];

void pre(int now){
	if(now>n-B) return bass[A]=1,void();
	if(!(A&adj[now])) A|=(1<<now-1),pre(now+1),A^=(1<<now-1);
	pre(now+1);
}

void sum(int now){
	if(now>n-B) return sos[S]+=bass[A],void();
	if(S&(1<<now-1)) A|=(1<<now-1),sum(now+1),A^=(1<<now-1);
	sum(now+1);
}

int G[50][50];

ll NoAdj(){
	B=n*log2(3)/log2(6);
	for(int i=0;i<n-B;++i) F|=(1<<i);
	for(int i=1;i<=n-B;++i) for(int j=1;j<=n-B;++j) adj[i]|=(G[i+B][j+B]<<(j-1));
	pre(1);for(S=0;S<(1<<n-B);++S) sum(1);
	for(int i=1;i<=B;++i){
		adj[i]=0;for(int j=1;j<=B;++j) adj[i]|=(G[i][j]<<(j-1));
	}
	for(int i=1;i<=B;++i) for(int j=B+1;j<=n;++j) sad[i]|=(G[i][j]<<(j-B-1));
	S=0;whl(1);return ans;
}

int co[50];bool flg;

void chkBin(int now,int c){
	if(co[now]){
		flg|=(co[now]!=c);return ;
	}
	co[now]=c;for(int i=1;i<=n;++i) if(G[now][i]) chkBin(i,((c-1)^1)+1);
}

int blk;

ll Ol1(){
	for(int i=1;i<=n;++i) if(!co[i]) chkBin(i,1),++blk;
	if(flg) return 0;
	else return 1ll<<blk;
}

int main()
{
	int m,x,y,isc=0;scanf("%d%d",&n,&m);
	while(m--) scanf("%d%d",&x,&y),G[x][y]=G[y][x]=1;
	for(int i=1;i<=n;++i){
		bool flg=0;for(int j=1;j<=n;++j) flg|=G[i][j];
		isc+=!flg;
	}
	printf("%lld",(1ll<<n)-2*NoAdj()+Ol1()-(1ll<<blk)+2*(1ll<<isc));
}
posted @ 2023-08-23 09:41  Albertvαn  阅读(16)  评论(0编辑  收藏  举报