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