几个解决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];
	}
}

原文地址:https://www.cnblogs.com/CQzhangyu/p/8227483.html

时间: 2024-07-30 13:49:48

几个解决k染色问题的指数级做法的相关文章

滴滴在职iOS开发者,告诉你他是如何实现指数级提升开发技术的?

前言: 如何提升开发技术的方法很多,比如专注,刻苦,热情,兴趣等,不过我这里不会提这些,下面想说的是我觉得能够指数级提升的窍门和一些自己在求索路上的一些体会,也算是一个阶段性的总结吧.给大家做个分享,希望对需要的同学有用. 窍门一,将代码放到GitHHub 上 看到这个标题一般人的反应就是觉得自己的代码和那些高大上的开源库比起来相形见绌,有种拿不出手的感觉.但是要想提高技术,是提高自己的技术,只要和自己比就好了.将代码发出来不是献丑而是为了交流,交流就会获得信息,都说信息时代科技进步都是指数级,

云科技时代,华为要创造企业生态圈的指数级繁荣

2017年,华为EBG中国区的营收同比增长超过了40%,而合作伙伴的业绩也在发生巨大的变化:2017年首次出现了过百亿营收的合作伙伴,同时除总经销商外,还有两家营收过10亿的合作伙伴(VAD增值合作伙伴).以及60多家营收过1亿.600多家营收过千万的合作伙伴,而云合作伙伴也达到近2000家.这是华为EBG中国区总裁蔡英华在近日推介将于3月22-23日在青岛举办的华为中国生态伙伴大会2018时所透露的. 2017年华为首次提出了生态伙伴的概念,并把往年的合作伙伴大会改名为生态伙伴大会,同时在会上

少是指数级的多(转)

转自 http://www.oschina.net/news/30584 原文 Less is exponentially more是 Rob Pike 自己整理的他在六月22日,旧金山的 Golang 会议上的演讲稿.清晰的介绍了 Go 的前世今生,来龙去脉.为了让更多的人能够更加清楚的认识到 Go 的优雅并喜爱上 Go,特翻译成中文,以飧读者. ------翻译分隔线------ 大道至简 这是我(Rob Pike)在 2012 年六月,旧金山 Go 会议上的演讲内容. 这是一个私人演讲.我

关于求解区间第k大的在线和离线做法

最近做了一道关于整体二分的题. 很开心地涉足了关于求区间第k大问题. 问题:给定序列,若干询问,求区间第k小. 第k大类推. 离线算法: 整体二分. 将所有询问离线下来,挂在区间右端点先. 对所有询问二分答案mid. 那么序列上的数就可以划分为两类了,一类小于等于mid,一类大于mid. 用树状数组维护一下. 接着处理询问,对于每个当前的询问判断是否合法,然后将询问划分为两边,一边答案小于等于mid的,一边是大于mid的. 看一下复杂度,每次答案的范围除以2,每次二分分别扫描一次序列和询问,总复

容斥原理

对容斥原理的描述 容斥原理是一种重要的组合数学方法,可以让你求解任意大小的集合,或者计算复合事件的概率. 描述 容斥原理可以描述如下: 要计算几个集合并集的大小,我们要先将所有单个集合的大小计算出来,然后减去所有两个集合相交的部分,再加回所有三个集合相交的部分,再减去所有四个集合相交的部分,依此类推,一直计算到所有集合相交的部分. 关于集合的原理公式 上述描述的公式形式可以表示如下:                  它可以写得更简洁一些,我们将B作为所有Ai的集合,那么容斥原理就变成了: 这个

【转载】【容斥原理】

转载自 http://www.cppblog.com/vici/archive/2011/09/05/155103.html 容斥原理(翻译) 前言: 这篇文章发表于http://e-maxx.ru/algo/inclusion_exclusion_principle,原文是俄语的.由于文章确实很实用,而且鉴于国内俄文资料翻译的匮乏,我下决心将其翻译之.由于俄语对我来说如同乱码,而用Google直接翻译中文的话又变得面目全非,所以只能先用Google翻译成英语,再反复读,慢慢理解英语的意思,实在

容斥原理 (转载)

前言: 这篇文章发表于http://e-maxx.ru/algo/inclusion_exclusion_principle,原文是俄语的.由于文章确实很实用,而且鉴于国内俄文资料翻译的匮乏,我下决心将其翻译之.由于俄语对我来说如同乱码,而用Google直接翻译中文的话又变得面目全非,所以只能先用Google翻译成英语,再反复读,慢慢理解英语的意思,实在是弄得我头昏脑胀.因此在理解文章意思然后翻译成中文的时候,中文都不知道如何表述了.而又由于我对容斥原理知识的匮乏,很可能有些地方我的表述是错误的

浅谈动态规划

动态规划算法(Dynamic Programming,简称 DP)似乎是一种很高深莫测的算法,你会在一些面试或算法书籍的高级技巧部分看到相关内容,什么状态转移方程,重叠子问题,最优子结构等高大上的词汇也可能让你望而却步. 而且,当你去看用动态规划解决某个问题的代码时,你会觉得这样解决问题竟然如此巧妙,但却难以理解,你可能惊讶于人家是怎么想到这种解法的. 实际上,动态规划是一种常见的「算法设计技巧」,并没有什么高深莫测,至于各种高大上的术语,那是吓唬别人用的,只要你亲自体验几把,这些名词的含义其实

动态规划算法(Dynamic Programming,简称 DP)

动态规划算法(Dynamic Programming,简称 DP)似乎是一种很高深莫测的算法,你会在一些面试或算法书籍的高级技巧部分看到相关内容,什么状态转移方程,重叠子问题,最优子结构等高大上的词汇也可能让你望而却步. 而且,当你去看用动态规划解决某个问题的代码时,你会觉得这样解决问题竟然如此巧妙,但却难以理解,你可能惊讶于人家是怎么想到这种解法的. 实际上,动态规划是一种常见的「算法设计技巧」,并没有什么高深莫测,至于各种高大上的术语,那是吓唬别人用的,只要你亲自体验几把,这些名词的含义其实