G. Graph And Numbers
题意:
输入
n
(
40
)
,
m
n(40),m
n(40),m,表示点和边,现在点上写0或1,边的值为两端点值的和。
问有多少种写方案使边值至少有一个0,至少有一个1,至少有一个2。
题解:
- 这种出现至少求方案,一般用容斥:
设 f 0 , 1 , 2 f_{0,1,2} f0,1,2表示边值在集合 0 , 1 , 2 0,1,2 0,1,2的方案数;
f 0 , 1 f_{0,1} f0,1表示边值在集合 0 , 1 0,1 0,1的方案数;后面集合类似。
a n s = f 0 , 1 , 2 − f 0 , 1 − f 0 , 2 − f 1 , 2 + f 0 + f 1 + f 2 − f ans=f_{0,1,2}-f_{0,1}-f_{0,2}-f_{1,2}+f_{0}+f_{1}+f_{2}-f_{} ans=f0,1,2−f0,1−f0,2−f1,2+f0+f1+f2−f
1、 f 0 , 1 , 2 f_{0,1,2} f0,1,2显然是点上 0 , 1 0,1 0,1任意填;方案数 2 n 2^n 2n;
2、 f f_{} f,空集,只有边数为 0 0 0时,方案数为 2 n 2^n 2n,否则为 0 0 0;
3、 f 0 f 2 f_{0}f_{2} f0f2类似,只有孤立点可以随意填,其它的联通块只能填 0 ( 1 ) 0(1) 0(1)设孤立点个数 x x x,方案数 2 x 2^x 2x;
4、 f 1 f_{1} f1,对于每一个联通块都要形成二分图,否则为0,二分图个数 x x x,方案数 2 x 2^x 2x;
5、 f 0 , 2 f_{0,2} f0,2,联通块里填的数一样,联通块个数 x x x,方案数 2 x 2^x 2x;
6、 f 0 , 1 , f 1 , 2 f_{0,1},f_{1,2} f0,1,f1,2对称的,方案数一样,就算 f 0 , 1 f_{0,1} f0,1吧。 - 如何计算
f
0
,
1
f_{0,1}
f0,1,发现这是一个NP完全问题,而
n
n
n只有40,提示可以用折半搜索(meet-in-the-middle)
把点集分为两部分,先把一部分的值算出来, l e f [ 1 < < 21 ] lef[1<<21] lef[1<<21],然后把它转化为右半部分需要的值,最后枚举右半部分的状态,把左半部分的贡献加上就好。
代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
//#define int ll
ll g[49];
int n,m;
int f[49],siz[49];
int getfa(int x){return f[x]==x?x:f[x]=getfa(f[x]);}
void hebin(int x,int y){
int fx=getfa(x),fy=getfa(y);
if(fx!=fy)f[fx]=fy,siz[fy]+=siz[fx];
}
int check(int u){
int vis[49];
memset(vis,0,sizeof(vis));
vis[u]=1;
queue<int>q;
while(!q.empty())q.pop();
q.push(u);
while(!q.empty()){
int u=q.front();q.pop();
for(int i=0;i<n;i++)if(g[u]&(1ll<<i)){
if(vis[i]==0)vis[i]=3-vis[u],q.push(i);
else if(vis[i]!=3-vis[u])return 0;
}
}
return 2;
}
ll solve1(){
ll cnt=1,cnt2=1;
for(int i=0;i<n;i++)if(f[i]==i){
cnt<<=1;
cnt2*=check(i);
}
// cout<<cnt<<" "<<cnt2<<endl;
return -cnt+cnt2;
}
ll lef[1<<21];
void dfs1(int st,int ed,ll zt,ll lim){
if(st==ed){
lef[zt]++;return;
}
dfs1(st+1,ed,zt,lim);
if(((1ll<<st)&lim)==0)dfs1(st+1,ed,zt|(1ll<<st),lim|g[st]);
}
ll dfs2(int st,int ed,ll zt,ll lim){
if(st==ed){
zt&=lim;zt^=lim;
return lef[zt];
}
ll ans=dfs2(st+1,ed,zt,lim);
if((zt&(1ll<<st))==0)ans+=dfs2(st+1,ed,zt|g[st],lim);
return ans;
}
ll solve2(){
ll mid=n/2,lim=(1ll<<mid)-1;
dfs1(0,mid,0,0);
for(int i=0;i<mid;i++)
for(int j=0;j<=lim;j++)
if(j&(1ll<<i))lef[j]+=lef[j^(1ll<<i)];
return dfs2(mid,n,0,lim);
}
int main(){
//freopen("tt.in","r",stdin),freopen("tt.out","w",stdout);
cin>>n>>m;
for(int i=0;i<n;i++)f[i]=i,siz[i]=1;
for(int i=1,u,v;i<=m;i++)cin>>u>>v,u--,v--,g[u]|=(1ll<<v),g[v]|=(1ll<<u),hebin(u,v);
ll ans=1ll<<n;if(m==0)ans=0;
int cnt=0;
for(int i=0;i<n;i++){if(g[i]==0)cnt++;}
ans+=(1ll<<cnt+1);
ans+=solve1();
ans-=solve2()*2;
cout<<ans<<endl;
return 0;
}