bzoj 2303: [Apio2011]方格染色 (并查集)

题目描述

传送门

题目大意:将每个方格都染成红色或蓝色。要表格中每个2 × 2的方形区域都包含奇数个(1 个或 3 个)红色方格。有些格子的颜色已经染好了,求给剩下的方格染上颜色,使得整个表格仍然满足要求的染色方案数

题解

对于每个一个2*2的表格,我们可以把条件表示成异或方程的形式。
对于每个位置(i,j),若填红色那么 a[i][j]=1
S(i,j) = a[i][j] ^ a[i1][j1] ^ a[i1][j] ^ a[i][j1]=1
然后对于每个位置维护S(i,j)的前缀和,那么 a[1][1] ^ a[1][j] ^ a[i][1] ^ a[i][j] =!(i,j都是偶数)
可以发现只有i,j都是偶数的时候,前缀中才有奇数个方程,而且只有 a[1][1] a[1][j] a[i][1] a[i][j] 出现了奇数次。
然后可以发现染色的方案其实只与第一行第一列的点有关系。
那么如果枚举(1,1)的取值,那么对于所有给出的(i,j),我们将式子变成 a[1][j] ^ a[i][1] =!(i,j都是偶数)^ a[i][j] ^ a[1][1] 。如果右边的值为1,那么说明 a[1][j],a[i][1] 的取值相同,否则不同。那么如果我们用并查集将所有的关系连接的话,在一个连通块中的取值只要其中一个确定了剩余的取值都是唯一的。
那么答案其实就是2^(连通块个数-1),为什么要-1?因为(1,1)的值我们已经枚举过了,所以取值是位置确定的。
那么我们考虑如何用并查集连接,因为既有不等关系又有相等关系,所以我们可以维护加权并查集,g[i]表示i到代表元素是相等还是不相等,1表示不相等。那么每次合并的时候只有异或一下就好了,两个不等号不就又变成相等了嘛。另外我们在合并的时候还可以判断一下无解,如果两个东西在一个集合中我们可以得到他们之间的关系(相等或者不相等),如果和当前要连接的关系冲突,那么无解。
还有就是本身在第一行或者第一列中的点。我们可以直接将他们连到(1,1)上去,反正值已经确定了,如果取值相同g[i]=0,否则g[i]=1.
还要特判一下(1,1)是否被限制了,计算的时候需要特判。
对于一个点在第一行或者第一列,如果被限制了两次,且两次的值不同,那么需要特判掉无解的情况。

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define N 2000003
#define LL long long 
#define p 1000000000
using namespace std;
struct data{
    int x,y,opt,v;
}a[N];
int fa[N],F[N],n,m,k,mark[N],col[N],g[N];
int find(int x)
{
    if (fa[x]==x) return x;
    int t=find(fa[x]);
    g[x]^=g[fa[x]];
    fa[x]=t;
    return fa[x];
}
LL quickpow(int num,int x)
{
    LL base=num; LL ans=1;
    while (x){
        if (x&1) ans=ans*base%p;
        x>>=1;
        base=base*base%p;
    }
    return ans;
}
int cmp(data a,data b)
{
    return a.opt<b.opt||a.opt==b.opt&&a.v<b.v;
}
LL solve()
{
    for (int i=1;i<=n+m;i++) fa[i]=i,g[i]=0;
    fa[n+1]=1;
    for (int i=1;i<=n+m;i++) 
     if (mark[i]) {
        fa[i]=1;
        if (col[1]==col[i]) g[i]=0;
        else g[i]=1;
     }
    sort(a+1,a+k+1,cmp);int K=0;
    for (int i=k;i>=1;i--) 
     if (!a[i].opt) {
        K=i;
        break;
     }
    for (int i=1;i<=K;i++) {
        int r1=find(a[i].x); int r2=find(a[i].y+n); 
        int t=g[a[i].x]^g[a[i].y+n]^a[i].v;
        if (r1!=r2) fa[r2]=r1,g[r2]=t;
        else if (t) return 0; 
    }
    int ans=0;
    for (int i=1;i<=n+m;i++) 
     if (find(i)==i&&!mark[i]) ans++;
    //cout<<ans<<endl; 
    return quickpow(2,max(0,ans-1));
}
int main()
{
    freopen("a.in","r",stdin);
    freopen("my.out","w",stdout);
    scanf("%d%d%d",&n,&m,&k);
    bool p0,p1; p0=p1=true;
    for (int i=1;i<=k;i++) {
        scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].v);
        if (a[i].x==1&&a[i].y==1) {
            if (a[i].v) p0=false;
            else p1=false; 
            a[i].opt=1;
        }
        else{
         if (!(a[i].x&1)&&!(a[i].y&1)) 
          a[i].v^=1;
         if (a[i].x==1) {
          if (!mark[a[i].y+n])mark[a[i].y+n]=1,col[a[i].y+n]=a[i].v,a[i].opt=1;
          else {
           if (col[a[i].y+n]!=a[i].v) {
            printf("0\n");
            return 0;
           }else  a[i].opt=1;
          }
         } 
         if (a[i].y==1){
          if (!mark[a[i].x]) mark[a[i].x]=1,col[a[i].x]=a[i].v,a[i].opt=1;
          else{
           if (col[a[i].x]!=a[i].v) {
            printf("0\n");
            return 0;
           }else a[i].opt=1;
          }
         }
        }
    }
    LL ans0=0,ans1=0;
    if (p0) col[1]=0,ans0=solve();
    if (p1) {
        col[1]=1;
        for (int i=1;i<=k;i++) 
         a[i].v^=1;
        ans1=solve();
    }
    printf("%lld\n",(ans0+ans1)%p);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值