其他分享
首页 > 其他分享> > CF1221G Graph And Numbers

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