CF1221G 题解
特判 \(m=0\)。然后考虑做一个容斥。令 \(F(E)\;(E\subseteq\{0,1,2\})\) 表示边权集合 \(\subseteq E\)(没有不在 \(E\) 内的数字)的方案数。那么答案为
对原图求孤点(所在连通块大小为 \(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]\) 暴力枚举子集啊。分析复杂度:
令 \(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));
}