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的点集

因此需要一个父集前缀和

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
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);
}