1. 程式人生 > 實用技巧 >【題解】P3631 [APIO2011]方格染色

【題解】P3631 [APIO2011]方格染色

Tag

並查集,異或方程

很有意思的一道題,所以單獨拿出來了。

完整分享看 這裡

題目連結
luogu

題意

有一個包含 \(n \times m\) 個方格的表格。要將其中的每個方格都染成紅色或藍色。表格中每個 \(2 \times 2\) 的方形區域都包含奇數個( \(1\) 個或 \(3\) 個)紅色方格。例如,下面是一個合法的表格染色方案(R 代表紅色,B 代表藍色):

B B R B R
R B B B B
R R B R B

表格中的一些方格已經染上了顏色.求給剩下的表格染色,使得符合要求的方案數。

思路

每天一道壓軸好題。 其實這題跟並查集沒啥關係,只是用來維護而已

題意可以簡化為:在 \(n\times m\)

的矩陣中放 01,k 個格子已經放好了,要放滿,且每個 \(2\times 2\) 的格子中有奇數個1.

由題意可知,任意四個格子(二乘二)的異或值為 1,不斷異或相鄰的兩個“矩形”的異或式子 (如:\(A\oplus B\oplus C\oplus D=C\oplus D\oplus E\oplus F=E\oplus F\oplus G\oplus H=1\),選取相鄰的式子得到 \(A\oplus B\oplus E\oplus F=0,A\oplus B\oplus G\oplus H=0\) )

由這個思路推廣,設 \(A(1,1),B(2,1),C(1,j),D(i,1)\)

  1. \(C,D\)
    在奇數列上, \(A\oplus B\oplus C\oplus D=0,E\oplus F\oplus G\oplus H=0,=> A\oplus C\oplus F\oplus H=0,A\oplus H=C\oplus F.\)
  2. \(C,D\) 在偶數列上。\(A\oplus B\oplus C\oplus D=1,E\oplus F\oplus G\oplus H=1.\) 此時,當 \(H\) 在偶數行,\(1\oplus A\oplus H=C\oplus F\) ;如果在奇數行,則有 \(A\oplus H=C\oplus F.\)

綜上所述,對於任意 \(H(i,j):\)

如果 \(i|2,j|2\) ,那麼 \(1\oplus (1,1)\oplus (i,j)=(1,j)\oplus (i,1)\) ;否則 \((1,1)\oplus (i,j)=(1,j)\oplus (i,1)\)

這樣就轉化為對 \((1,j),(i,1)\) 的約束。如果 \((1,1)\) 沒有給出,那麼就要列舉兩種情況。

用並查集維護。\(x\oplus y=0\) 時,合併 \((x,y),(x',y')\) ;否則合併 \((x,y'),(x',y)\)。無解特判就是 \(x,x'\in S\) (屬於同一個集合)

合併完成之後得到連通塊個數 \(sum\) ,列舉所有已知點(注意 \((1,1)\) 不算),去掉他們的連通塊,剩下的就是未知個數,\(2^{sum'}\) 即為方案。把 \((1,1)\) 的兩種情況相加即可。

注意:此題由於有虛點( \(x',y'\) ),所以空間要兩倍。

程式碼

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int mod=1e9,N=2e5+10;
int n,m,k,x[N],y[N],z[N],fa[N],g[N];

ll power( ll a,ll b )
{
        ll res=1;
        for ( ; b; b>>=1,a=a*a%mod )
                if ( b&1 ) res=res*a%mod;
        return res;
}

int find( int x )
{
        if ( x==fa[x] ) return x;
        int fat=find( fa[x] ); g[x]^=g[fa[x]]; 
        return fa[x]=fat;
}

int calc( int opt )
{
        for ( int i=1; i<=n+m; i++ )
                fa[i]=i,g[i]=0;
        fa[n+1]=1;
        if ( opt==1 )
                for ( int i=1; i<=k; i++ )
                        if ( x[i]>1 && y[i]>1 ) z[i]^=1;
        for ( int i=1; i<=k; i++ )
        {
                int x=:: x[i],y=:: y[i],z=:: z[i];
                if ( x!=1 || y!=1 )
                {
                        int fx=find(x),fy=find(y+n),ty=g[x]^g[n+y]^z;
                        if ( fx!=fy ) fa[fy]=fx,g[fy]=ty;
                        else if ( ty ) return 0;
                }
        }
        int res=0;
        for ( int i=1; i<=n+m; i++ )
                if ( i==find(i) ) res++;
        return power( 2,res-1 );
}

int main()
{
        scanf( "%d%d%d",&n,&m,&k );
        int flag=-1;
        for ( int i=1; i<=k; i++ )
        {
                scanf( "%d%d%d",&x[i],&y[i],&z[i] );
                if ( (!(x[i]&1)) && (!(y[i]&1)) ) z[i]^=1;
                if ( x[i]==1 && y[i]==1 ) flag=z[i];
        }

        if ( flag!=-1 ) printf( "%d\n",calc( flag ) );
        else printf( "%d\n",(calc(0)+calc(1))%mod );       
}