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);
}