CF1221G Graph And Numbers
作者:互联网
written on 2022-05-06
鸣谢@uid13237的代码提供了我能理解的思路
这是一道计数题。
初见这题,有些束手无策,但是题目给出了三个限制,那么我们对于这种有限制的计数题,可以考虑容斥。
大体思路就是容斥,想到这点,后面的大部分过程就很简单了,中间的过程可以参照这篇题解的,因为懒得打了。
此题最闹心的部分在于对于没有 \(0\) 和没有 \(2\) 的部分,两种情况总数相同,下面我们不妨以没有 \(2\) 为例作分析。
这种情况不像其它情况那样有直观的方法计算。观察数据范围,40对于二进制枚举来说太大,对于 dp 来说又小得可怜,因此这里我们采用的方法是 折半搜索 。
折半搜索适应的数据范围一般是 40~45 左右,它的大体思路是枚举一半,统计另一半满足条件的数量。对于这道题,我们不妨将 \(n\) 个数分成两半,采用二进制思想,若二进制位为 \(1\) 则对应原位置填 \(1\) ,那么因为没有 \(2\) ,所以一条边连着的两端点就不能同时为 \(1\) 。
我们考虑在右部内,先单独统计出满足条件的那些部分。这里有一个显然的结论,即:如果一个二进制状态满足条件,那么它的子集均满足条件(因为 \(1 少了\))。于是统计出满足条件的状态后,我们用SOSdp来做一个高维前缀和来保证时间复杂度。
高维前缀和代码
for(int i=0;i<p;i++) for(int j=0;j<(1<<p);j++) if(j&(1<<i)) R[j]+=R[j^(1<<i)];
最后在左部内先筛选出满足条件的二进制状态,对应到右部中那些可以与其匹配的方案,直接累加答案即可。
中间的一个小细节:开一个变量 \(x\) ,若某一位为 \(1\) ,则说明这一位不能选,用全集减去 \(x\) ,即 \(x\) 的补集也就是满足条件的最大右部了。
代码
#include<bits/stdc++.h>
#define N 45
#define M 4005
using namespace std;
typedef long long ll;
int n,m,p,k,fa[N];
int get(int x){return x==fa[x]?x:fa[x]=get(fa[x]);}
bool flag;
int oth(int x){return x==1?2:1;}
int tot,ver[M],nxt[M],head[N];
void add_E(int x,int y){ver[++tot]=y,nxt[tot]=head[x],head[x]=tot;}
int col[N];
void Col(int x)
{
for(int i=head[x];i;i=nxt[i])
{
int y=ver[i];
if(flag) return ;
if(col[y])
{
if(col[y]!=oth(col[x])) flag=1;
continue;
}
col[y]=oth(col[x]);
Col(y);
}
}
/*bool check(int s)
{
memset(ans,0,sizeof(ans));
for(int i=0;i<k;i++)
{
if(s&(1<<i)) ans[i+1]=1;
}
for(int x=1;x<=k;x++)
{
for(int i=head[x];i;i=nxt[i])
{
int y=ver[i];
if(y>k) continue;
if(ans[y]+ans[x]==0) return 0;
}
}
return 1;
}
ll calc()
{
ll res=0;
for(int i=0;i<(1<<n);i++)
if(check(i)) res++;
return res;
}*/
bool w[N][N];
ll R[(1<<20)+5];
ll calc()
{
for(int i=0;i<(1<<p);i++)
{
bool flag=0;
for(int j=0;j<p;j++)
{
if((i&(1<<j))==0) continue;
for(int l=0;l<p;l++)
{
if((i&(1<<l))==0) continue;
if(w[l+k+1][j+k+1])
{
flag=1;
break;
}
}
if(flag) break;
}
if(!flag) R[i]=1;
}
for(int i=0;i<p;i++) for(int j=0;j<(1<<p);j++) if(j&(1<<i)) R[j]+=R[j^(1<<i)];//sosdp
//一个数若可以,则它的子集一定都可以
ll res=0;
for(int i=0;i<(1<<k);i++)
{
bool flag=0;
for(int j=0;j<k;j++)
{
if((i&(1<<j))==0) continue;
for(int l=0;l<k;l++)
{
if((i&(1<<l))==0) continue;
if(w[l+1][j+1])
{
flag=1;
break;
}
}
if(flag) break;
}
if(flag) continue;
int x=0;
for(int j=0;j<k;j++)
{
if((i&(1<<j))==0) continue;
for(int l=k;l<n;l++)
{
if(w[j+1][l+1]) x|=(1<<(l-k));
}
}
res+=R[((1<<p)-1)^x];
}
return res;
}
int mark[N];
int main()
{
scanf("%d%d",&n,&m);if(m==0) printf("0"),exit(0);
k=n>>1,p=n-k;
for(int i=1;i<=n;i++) fa[i]=i;
for(int i=1;i<=m;i++)
{
int x,y;
scanf("%d%d",&x,&y);
fa[get(x)]=get(y);
add_E(x,y),add_E(y,x);
w[x][y]=w[y][x]=1;
}
int cnt=0,cnt2=0;
for(int i=1;i<=n;i++)
{
int fx=get(i);
if(fx==i) cnt++;
mark[fx]++;
}
for(int i=1;i<=n;i++) if(mark[i]==1) cnt2++;
for(int i=1;i<=n;i++)
{
if(!col[i]) col[i]=1,Col(i);
if(flag) break;
}
ll now=calc();
// printf("now=%lld\n",now);
ll all=pow(2,n);
all-=pow(2,cnt),all-=now*2,all+=(flag==0?pow(2,cnt):0),all+=pow(2,cnt2+1);
printf("%lld",all);
}
标签:满足条件,return,int,Graph,tot,fa,CF1221G,Numbers,col 来源: https://www.cnblogs.com/Freshair-qprt/p/16537764.html