【BZOJ2303】【Apio2011】方格染色 异或方程+并查集

链接:

#include <stdio.h>
int main()
{
    puts("转载请注明出处[vmurder]谢谢");
    puts("网址:blog.csdn.net/vmurder/article/details/45081583");
}

题解:

首先我们发现对于 ai,j 有下列式子:
ai,j xor ai+1,j xor ai,j+1 xor ai+1,j+1==1
然后推导得到对于 ai,j 有下列式子:
a1,1 xor a1,j xor ai,1 xor ai,j==!i,j

然后显然如果我们先涂上第一行和第一列,剩下的点的染色方案是唯一的。
而这些已经的填涂 (i,j) 则成了 (1,j) (i,1) 这俩点之间的限制,即方程四点异或和确定。

我们可以先枚举 (1,1) 0 还是 1 ,然后其它的染色作为点之间关系跑并查集。
然后会剩下 x 个连通块,刨去 1 的一个,则此种对 1 染色的方案(染0或者染1)的答案贡献就是 2x1

实现:

一些定义:
一个被提前染色了的点有染色权值为其染的色,而如果这个点横纵坐标皆为偶数,则权值取反。

首先说左上角为 0 时的答案计算:
对于点 (i,j),i>1j>1 则它是 点 i 和 点j+n 之间的限制,表示这两点之间差异为其染色权值。
【原因:如果权值为1,左上角还是0,那么俩点最终染色必须不一样对吧,反之亦然】
对于其它情况,即点在第一行或第一列上,我们认为这是在表示此点和点 (1,1) 的关系,处理方式可以与上一种情况相同(甚至在我的实现下代码也是相同哒,不用特殊考虑!)
【原因:如果权值为1,表示与 (0,0) 目前 0 的染色不同。】

然后对于左上角是 1 时的答案计算:
我们只需要把所有点的染色权值取反,然后推一下发现正好可以跟上述做相同处理。
也就是直接调用上述流程就可以啦!!

特殊问题:
如果点 (1,1) 被染色,那么需要特殊判定上面对 1 <script type="math/tex" id="MathJax-Element-24">1</script> 的枚举。

代码:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define N 1001000
#define mod 1000000000
#define A e[i].x
#define B e[i].y+n
#define C e[i].z
using namespace std;
struct Eli
{
    int x,y,z;
    bool read()
    {
        scanf("%d%d%d",&x,&y,&z);
        if(x==1&&y==1)return 1;
        if(!((x|y)&1))z^=1;
        return 0;
    }
}e[N];
int f[N<<1];
bool g[N<<1];
int find(int x)
{
    if(f[x]==x)return x;
    find(f[x]);
    g[x]^=g[f[x]];
    return f[x]=f[f[x]];
}
long long power(long long x,long long p)
{
    long long ret=1;
    while(p)
    {
        if(p&1)ret=(ret*x)%mod;
        x=(x*x)%mod,p>>=1;
    }
    return ret;
}
int n,m,p;
long long getans()
{
    int i,fa,fb,t;
    for(i=1;i<=n+m;i++)f[i]=i,g[i]=0;
    f[n+1]=1;
    for(i=1;i<=p;i++)
    {
        fa=find(A),fb=find(B),t=g[A]^g[B]^C;
        if(fa!=fb)f[fb]=fa,g[fb]=t;
        else if(t)return 0;
    }
    long long ans=0;
    if(i>p)for(ans=0,i=1;i<=n+m;i++)
        if(find(i)==i)ans++;
    return power(2,ans-1);
}
bool f1=1,f2=1;
long long ans1,ans2;
int main()
{
    scanf("%d%d%d",&n,&m,&p);
    for(int i=1;i<=p;i++)
    {
        if(e[i].read())
        {
            if(e[i].z)f1=0;
            else f2=0;
            i--,p--;
        }
    }

    if(f1)ans1=getans();
    if(f2)
    {
        for(int i=1;i<=p;i++)
            if(e[i].x>1&&e[i].y>1)e[i].z^=1;
        ans2=getans();
    }

    cout<<(ans1+ans2)%mod<<endl;
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值