几个解决k染色问题的指数级做法

几个解决k染色问题的指数级做法

                ——以及CF908H题解

给你一张n个点的普通无向图,让你给每个点染上k种颜色中的一种,要求对于每条边,两个端点的颜色不能相同,问你是否存在一种可行方案,或是让你输出一种可行方案,或是让你求出满足条件的最小的k。这种问题叫做k染色问题。众所周知,当k>2时,k染色问题是NP的。但是相比$O(k^n)$的暴力搜索来说,人们还是找到了很多复杂度比较优越的指数级做法。本文简单介绍其中几种。

因为对于$O(n^22^n)$来说,$O(n^2)$小得可以忽略不计,所以在本文中我们用$O^*(2^n)$来代替$O(n^{...}2^n)$。

三染色问题

k=3的情况显然比其它的情况更容易入手一些,下面给出几种简单的方法:

1.生成树

先任意求出一个原图的生成树,而对一个生成树染色有$3\cdot 2^{n-1}$种方案,所以暴力即可。

2. 转成2分图

3染色问题可以看成将原图分为3个点独立集的问题。而3个独立集中最小的那个一定不超过$\frac n 3$,所以我们$C_n^{n\over 3}$搜索最小的这部分,然后剩余部分的变成二染色问题,可以在多项式时间内解决。复杂度$\approx O^*({27\over 4})^{n\over 3}\le O^*(1.89^n)$。

3. 随机化+2-SAT

对于每个点,随机扔掉一个颜色不选,然后建图跑2-SAT。因为每个点我们都有$2\over 3$的概率选到正确的颜色,所以期望复杂度是$O^*(1.5^n)​$。

当然还有更优越的,其中最厉害的复杂度是$O^*(1.3289^n)$,然而本人并不知道具体做法。。。

k染色问题

这里只讨论k染色的判定性问题,对于k染色的输出方案问题,我们可以不断向原图中加边直到不能k染色为止,此时只需要贪心的求出每个点的染色即可。

1.状压DP

我们带有一点归纳的思想,如果一个集合S能k染色,那么它一定能分成两个部分,一部分能1染色(独立集),一部分能k-1染色。所以我们可以暴力枚举将其分成哪两部分,然后DP即可。时间复杂度是枚举子集的$O^*(3^n)$。

2.容斥原理

考虑一个更难的版本,我们试图统计k染色的方案数,如果方案数>0则有解。(如何输出方案呢?我们可以不断试图向图中加边,最后用朴素的贪心进行染色即可)

k染色的方案数可以看成选出k个独立集,这些独立集覆盖所有点的方案数。由于我们只需要知道方案数是否>0,所以我们甚至可以让这些独立集相交或相同。我们令$c_k(G)$表示G中选出k个独立集覆盖整张图的方案数,考虑容斥,设X是G的一个诱导子图,设a(x)表示x里有多少个独立集。那么$a(x)^k$表示的就是在X中选出k个独立集的方案数(有标号的)。

$c_k(G)=\sum\limits_{X}(-1)^{n-|X|}a(X)^k$

如何求出a数组呢?考虑DP。

我们枚举X中任意一个点v,设$neighbor(v)$表示与v相邻的点的集合,那么$a(X)=1+a(X-v)+a(X-v-neighbor(v))$,分别代表v这个点,不包含v的独立集和包含v且含有至少一个其它点的独立集。

复杂度$O^*(2^n)​$。(已知的最优算法)。

3.FWT

我们可以预处理所有独立集,然后用FWT,不断和自己取或卷积,直到$2^n-1$不为0为止。复杂度$O^*(2^n)$。

以上参考:http://www.wisdom.weizmann.ac.il/~dinuri/courses/11-BoundaryPNP/L01.pdf

【CF908H】New Year and Boolean Bridges

题意:有一张未知的n个点的有向图,我们设f(a,b)=0/1,表示是否存在一条从a到b的路径。但是你并不知道f(a,b),取而代之的是,对于任意的a和b,你知道下面3个条件中的一个。

1.f(a,b) and f(b,a) =true
2.f(a,b) or f(b,a) =true
3.f(a,b) xor f(b,a) =true

现在给你n,再给你任意两个点a,b之间满足的条件,让你构造一张符合条件的原图。特别地,令m表示你的图中边的数量,我们希望你的m尽可能小,你只需要输出最小的m即可。

n<=47

题解:我们先将and关系形成的连通块缩到一起,如果连通块内存在xor关系则输出无解,否则,我们可以给出一种m可能不是最小的建图方法:

令所有大小>1的连通块内部形成一个环,再用一条链将所有环(以及单个的点)串起来。

这样的话,我们的花费是(n-1+环数)。而我们发现我们可以将某些环合并起来,前提是这些环之间不存在xor关系。如果我们将xor关系看成边,所有环看成点,那么这就变成了k染色问题!因为大小>1的连通块最多只有23个,所以指数级做法是可行的。

具体复杂度:如果用二分+容斥原理的话,复杂度是$O(log^2_n2^n)$的;如果用FWT的话,需要做n次FWT,复杂度是$O(n^22^n)$的,但你会发现每次你只需要对$2^n-1$进行逆FWT,所以复杂度可以优化为$O(n2^n)$。

细节:其实容斥原理或FWT得到的方案数是一个特别大的数,但你只关心这个数是不是0,所以可以采用自然溢出的方式解决。

容斥原理代码:

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
int g[(1<<23)+4],Log[(1<<23)+4],cnt[(1<<23)+4];
int n,m;
int f[50],siz[50],bel[50],nr[50];
char str[50][50];
int find(int x)
{
	return (f[x]==x)?x:(f[x]=find(f[x]));
}
inline int pm(int x,int y)
{
	int z=1;
	while(y)
	{
		if(y&1)	z=z*x;
		x=x*x,y>>=1;
	}
	return z;
}
bool check(int x)
{
	int ret=0,i;
	for(i=1;i<(1<<m);i++)	ret+=(((m^cnt[i])&1)?-1:1)*pm(g[i],x);
	return ret!=0;
}
int main()
{
	scanf("%d",&n);
	int i,j,a,b,l,r,mid;
	for(i=0;i<n;i++)
	{
		scanf("%s",str[i]),f[i]=i,siz[i]=1;
		for(j=0;j<i;j++)	if(str[i][j]=='A'&&find(i)!=find(j))	siz[f[j]]+=siz[f[i]],f[f[i]]=f[j];
	}
	memset(bel,-1,sizeof(bel));
	for(i=0;i<n;i++)	if(find(i)==i&&siz[i]>1)	bel[i]=m++;
	if(!m)
	{
		printf("%d",n-1);
		return 0;
	}
	for(i=0;i<n;i++)	for(j=0;j<i;j++)	if(str[i][j]=='X')
	{
		if(find(i)==find(j))
		{
			puts("-1");
			return 0;
		}
		a=bel[f[i]],b=bel[f[j]];
		if(a!=-1&&b!=-1)	nr[a]|=1<<b,nr[b]|=1<<a;
	}
	for(i=0;i<m;i++)	Log[1<<i]=i;
	for(i=1;i<(1<<m);i++)	a=Log[i&-i],g[i]=1+g[i^(1<<a)]+g[i^(1<<a)^(i&nr[a])],cnt[i]=cnt[i^(1<<a)]+1;
	l=1,r=m;
	while(l<r)
	{
		mid=(l+r)>>1;
		if(check(mid))	r=mid;
		else	l=mid+1;
	}
	printf("%d",n-1+r);
	return 0;
}

FWT代码:

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
int g[(1<<23)+4],Log[(1<<23)+4],sg[(1<<23)+4];
int n,m,len;
int f[50],siz[50],bel[50],nr[50];
char str[50][50];
int find(int x)
{
	return (f[x]==x)?x:(f[x]=find(f[x]));
}
inline int pm(int x,int y)
{
	int z=1;
	while(y)
	{
		if(y&1)	z=z*x;
		x=x*x,y>>=1;
	}
	return z;
}
inline void fwt(int *a)
{
	int h,i,j;
	for(h=2;h<=len;h<<=1)	for(i=0;i<len;i+=h)	for(j=i;j<i+h/2;j++)	a[j+h/2]+=a[j];
}
inline int ufwt(int i,int h)
{
	if(h==1)	return sg[i];
	return ufwt(i,h>>1)-ufwt(i-h/2,h>>1);
}
int main()
{
	scanf("%d",&n);
	int i,j,a,b;
	for(i=0;i<n;i++)
	{
		scanf("%s",str[i]),f[i]=i,siz[i]=1;
		for(j=0;j<i;j++)	if(str[i][j]=='A'&&find(i)!=find(j))	siz[f[j]]+=siz[f[i]],f[f[i]]=f[j];
	}
	memset(bel,-1,sizeof(bel));
	for(i=0;i<n;i++)	if(find(i)==i&&siz[i]>1)	bel[i]=m++;
	if(!m)
	{
		printf("%d",n-1);
		return 0;
	}
	for(i=0;i<n;i++)	for(j=0;j<i;j++)	if(str[i][j]=='X')
	{
		if(find(i)==find(j))
		{
			puts("-1");
			return 0;
		}
		a=bel[f[i]],b=bel[f[j]];
		if(a!=-1&&b!=-1)	nr[a]|=1<<b,nr[b]|=1<<a;
	}
	for(i=0;i<m;i++)	Log[1<<i]=i;
	g[0]=1;
	len=1<<m;
	for(i=1;i<len;i++)	a=Log[i&-i],g[i]=g[i^(1<<a)]&(!(i&nr[a]));
	fwt(g);
	memcpy(sg,g,sizeof(sg));
	for(i=1;i<=m;i++)
	{
		if(ufwt(len-1,len)!=0)
		{
			printf("%d",n-1+i);
			return 0;
		}
		for(j=0;j<len;j++)	sg[j]*=g[j];
	}
}

 

posted @ 2018-01-07 10:32  CQzhangyu  阅读(2610)  评论(0编辑  收藏  举报