Educational Codeforces Round 73 (Rated for Div. 2)G. Graph And Numbers(图论+容斥+meet-in-the-middle)

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,2f0,1f0,2f1,2+f0+f1+f2f
    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;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值