Codeforces 1221 G Graph And Numbers

题面

    这种比赛时只有11个人做出来的题一般来说都是暴难的, 我也不知道我怎么搞出来的www

    看完这个题第一感觉就是要容斥,至少有一条某种边的方案已经比较难求了,而直接算三种边都至少存在一条的方案数就更难了2333

    那么不妨考虑从反面容斥吧

    设把三种边的存在情况表示成三进制的话,1表示至少有一条 ,0表示一条都没有,?表示这种边没有限制,那么容斥可以得到的是 : f[111] = f[???] - (f[0??]+f[?0?]+f[??0]) + (f[00?]+f[0?0]+f[?00]) - f[000]

    证明可以通过二项式系数的关系导出,并且可以推广到N维形式。

    显然等号右边的每个f[]都是比较好求的(但是会涉及很多算法),不过注意一些f[]是恒等的(根据图的对称性可得),所以不用每个f[]都去写一个函数算。算等号右边的f[]贡献了本题的大部分码量,这里就不一个一个说了,相信你们都能想出来的hhhhh

    最后注意一下f[000],当且仅当 m==0 时 f[000]=2^n;否则 f[000]=0。

    我一开始就因为这个WA了,想当然以为不可能每种边都没有(I'm reall a bro in bro),即 f[000]=0.

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1200005;

int n,m,v[55],p[55],M,f[N];
bool g[55][55];
ll ans,all;

inline bool Can(int S,int ad){
	for(int i=0;i<M;i++)
	    for(int j=i+1;j<M;j++) if(g[i+ad][j+ad]&&((1<<i)&S)&&((1<<j)&S)) return 0;
	return 1;
}

inline void Get1(){
	for(int s=0;s<(1<<M);s++) if(Can(s,0)) f[s]++;
}

inline void maintain(){
	for(int i=0;i<M;i++)
	    for(int j=0;j<(1<<M);j++) if(!((1<<i)&j)) f[j|(1<<i)]+=f[j];
}

inline ll Get2(){
	ll an=0;
	for(int s=0,now,al=(1<<M)-1;s<(1<<(n-M));s++) if(Can(s,M)){
		now=0;
		for(int i=M;i<n;i++) if((1<<(i-M))&s)
		    for(int j=0;j<M;j++) if(g[i][j]) now|=1<<j;
		an+=f[al^now];
	}
	return an;
}

inline ll solve1(){
/*  meet in the middle:
        一半: 枚举合法二进制并用FMT的处理(类似高维每维值域{0,1}的前缀和)
		      映射到所有包含它的二进制上
		      
		另一半: 枚举合法二进制,直接找FMT数组对应的位置加就OK了 
*/ 	
	Get1();
	maintain();
	return Get2();
}

int getfa(int x){ return p[x]==x?x:(p[x]=getfa(p[x]));}

inline ll solve2(){
	ll an=1;
	for(int i=0;i<n;i++) p[i]=i;
	
	for(int i=0,fa,fb;i<n;i++)
	    for(int j=i+1;j<n;j++) if(g[i][j]){
	    	fa=getfa(i),fb=getfa(j);
	    	if(fa!=fb) p[fa]=fb;
		}
	
	for(int i=0;i<n;i++) if(v[getfa(i)]!=2) v[p[i]]=2,an<<=1;
	
	return an;
} 

inline ll solve3(){
	ll an=1;
	
	for(int i=0;i<n;i++)
	    for(int j=i+1;j<n;j++) if(g[i][j]) v[i]=v[j]=3;
	for(int i=0;i<n;i++) if(v[i]!=3) an<<=1;
	
	return an;
}

bool color(int x,int c){
	v[x]=c;
	for(int i=0;i<n;i++) if(g[x][i])
	    if(v[i]==v[x]) return 0;
	    else if(v[i]<4&&!color(i,9-c)) return 0;
	return 1;
}

inline ll solve4(){
	ll an=1;
	
	for(int i=0;i<n;i++) if(v[i]<4)
	    if(!color(i,4)) return 0; else an<<=1;
	
	return an;
}

int main(){
	scanf("%d%d",&n,&m),all=(1ll<<n)-1,M=n+1>>1;
	if(!m) ans-=all+1;//000 type
	for(int U,V;m;m--)
	    scanf("%d%d",&U,&V),U--,V--,g[V][U]=g[U][V]=1;
	
	ans+=all+1;// ??? type
	ans-=2*solve1();// 0?? and ??0 type , cause its symmetry , we can simply double the ans
	ans-=solve2();// ?0? type
	ans+=2*solve3();// ?00 and 00? type , similar to solve1()
	ans+=solve4();//0?0 type
	
	cout<<ans<<endl;
	return 0;
}

  

猜你喜欢

转载自www.cnblogs.com/JYYHH/p/11574118.html
今日推荐