幾個解決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];
	}
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM