[BZOJ2303]-[Apio2011]方格染色-并查集+题目性质

说在前面

这道题真的好巧妙,看题解理解了好久…(ZKX:蠢死了)
交上去WA了,以为可能是哪里细节搞错了,然而最后发现是行和列搞反了= =???


题目

BZOJ2303传送门

题目大意

现在有一个N*M矩阵,矩阵上只能填数字0或1
现在矩阵里已经有一些格子被填写了数字,询问是否存在一种填写方案使得「任意一个2*2的矩阵异或和为1」,输出方案总数

输入输出格式

输入格式:
第一行N , M , K,表示一个N行M列的矩阵,其中已经有K个格子填了数字
接下来K行,每行3个数字x,y,c表示第x行第y列的格子被填写了数字c
N,M,K均不超过1e6

输出格式:
输出方案总数对1000000000(9个0)取模的结果


解法

i i j列的格子用 a[i][j] a [ i ] [ j ] 表示
考虑一个 22 2 ∗ 2 的矩阵,假设之右下角为 (i,j) ( i , j ) ,那么应该符合a[i][j] ^ a[i-1][j] ^ a[i][j-1] ^ a[i-1][j-1]=1,令这个式子为 S[i][j] S [ i ] [ j ]

把从左上角到 (i,j) ( i , j ) 所有的 S S ,即S[2…i][2…j]全部异或起来,最后会得到a[1][1] ^ a[1][j] ^ a[i][1] ^ a[i][j]=[ i,j都是偶数 ]
可以发现,只要确定了第一行和第一列数字的值,整个矩阵就已经确定下来了。如果不考虑已经填的格子,答案就是2M+N1
题目已经告诉了部分的a[i][j],而a[1][1]只有两种取值,确定了这两个,就可以知道a[1][j] ^ a[i][1]的值是多少,从而知道合法的方案数是多少。使用带权并查集维护这个异或关系(这个权,就是异或的值,如果冲突就可以直接判无解),最后答案就是 21 2 并 查 集 数 − 1

如果给的a[i][j]中有i=1或者j=1的情况,就相当于是已经确定了取值(而不是只知道异或关系),所以这样的i,j会和1在同一个并查集里,这个并查集里的数字都是确定的。这就是上面「并查集数-1」的原因


下面是自带大常数的代码

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std ;

int N , M , K , cnt , ans ;
bool flag[2] ;
struct Paint{
    int x , y , c ;
    Paint(){} ;
    Paint( const int &x_ , const int &y_ , const int &z_ ):
        x(x_) , y(y_) , c(z_){} ;
}P[1000005] ; 
struct Union_set{
    bool xorv[1000005*2] ;
    int fa[1000005*2] ;
    void init(){
        for( int i = 1 ; i < N + M ; i ++ )
            fa[i] = i , xorv[i] = 0 ;
    }
    int find( int x ){
        if( fa[x] == x ) return x ;
        int tmp = find( fa[x] ) ;
        xorv[x] ^= xorv[ fa[x] ] ;
        return fa[x] = tmp ;
    }
    bool Union( int a , int b , int xorv_ ){
        int F_a = find( a ) , F_b = find( b ) ;
        if( F_a == F_b ) return false ;
        fa[F_b] = F_a;
        xorv[F_b] = xorv_ ;
        return true ;
    }
}U ;

int mmod = 1e9 ;
int s_pow( int x , int b ){
    long long rt = 1 ;
    while( b ){
        if( b&1 ) rt = rt * x %mmod ;
        x = 1LL * x * x %mmod ; b >>= 1 ;
    }
    return rt ;
}

int solve(){
    //a[1][j]^a[i][1] = a[1][1]^( i,j偶? 1 : 0 )^a[i][j] ;
    U.init() ;
    for( int i = 1 ; i <= cnt ; i ++ ){
        int x = P[i].x , y = ( P[i].y == 1 ? 1 : P[i].y + N - 1 ) , c = P[i].c ;
        int F_x = U.find( x ) , F_y = U.find( y ) ;
        int tmp = U.xorv[x] ^ U.xorv[y] ^ c ;
        if( F_x == F_y && tmp ) return 0 ;
        U.Union( F_x , F_y , tmp ) ;
    }
    int mi = 0 ;
    for( int i = 1 ; i < N + M ; i ++ )
        if( U.fa[i] == i ) mi ++ ;
    return s_pow( 2 , mi-1 ) ;
}

int main(){
    flag[0] = flag[1] = true ;
    scanf( "%d%d%d" , &N , &M , &K ) ;
    for( int i = 1 , x , y , c ; i <= K ; i ++ ){
        scanf( "%d%d%d" , &x , &y , &c ) ;
        if( x == 1 && y == 1 ) flag[c] = false ;
        else P[++cnt] = Paint( x , y , c^(!(x&1)&&!(y&1)) ) ;
    }
    //a[1][1]^a[1][j]^a[i][1] = ( i,j偶? 1 : 0 )^a[i][j] ;
    if( flag[1] ) ans += solve( ) ;
    if( flag[0] ){
        for( int i = 1 ; i <= cnt ; i ++ )
            if( P[i].x > 1 && P[i].y > 1 ) P[i].c ^= 1 ;
        ans += solve( ) ;
    }
    printf( "%d" , ans%mmod ) ;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值