bzoj2303洛谷3631 异或推理+并查集

题目分析

首先发现,如果我们把蓝色看作1,把红色看作0(不过红配蓝什么的真的丑….),那么对于每一个格子a(i,j),都有:a(i,j)^a(i+1,j)^a(i,j+1)^a(i+1,j+1)=1
那么只要第一列和第一行确定了如何染色,整个表格的染色就确定了。(这是显然的,因为一个 22 的格子中有三个被染色了,剩下的格子颜色已定。)
现在我们把上面那个式子记作S(i,j),则我们对于每一个(i,j),求一次s(1,1)^s(1,2)^…^s(i-1,j-1),这个过程数学感不好的人(比如本蒟蒻),就要崩溃了。不过我们可以形象的想一想,相当于在一个 ij 的格子中用一块 22 的小毯子不停地移位覆盖,那么被盖了偶数次的一个a(x,y),就会发生a(x,y)^a(x,y)=0这样的事故,而0^0=0,0^1=1,所以就相当于消除了。那么被盖了奇数次的格子只有a(1,1),a(i,1),a(1,j)和a(i,j),因此,a(1,1)^a(i,1)^a(1,j)^a(i,j)在i和j均为偶数的时候(此时 (i1)(j1) 是奇数,s的异或前缀和是1)为1,否则为0.
那么我们可以枚举a(1,1)的值,然后因为a(i,j)的值和a(1,1)的异同关系,a(i,1)和a(1,j)的异同关系可以确定,用带权并查集维护所有有关系的点a(i,1)或a(1,j),然后有多少个集,答案就有2的多少次方的贡献。当然还要注意一点,有些集因为题目给出的条件所以已经确定了所有元素是1还是0,所以这些集对答案的贡献是1不是2.

代码实现

虽然题目分析已经很明了了,但是代码实现不够简洁,就容易如boshi大佬一样怀疑人生,怀疑鲁迅,怀疑枣树。
以下两个操作可以简洁代码:
1.如果a(1,1)=0,直接操作,如果枚举到a(1,1)=1,则把除了第一行和第一列以外的颜色都反过来即可。
2.带权并查集,通过异或等位运算操作进行判断,来简洁代码。给对于位运算操作不是很熟悉的人(比如本蒟蒻)的提醒:对于1和0,如果异或0,则不变,如果异或1,则发生变化。
还有本蒟蒻因为没取模WA了两次QAQ

代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<climits>
using namespace std;
#define LL long long
LL mod=1000000000;
const int N=100005;
int n,m,T;
int x[N],y[N],w[N],f[N<<1],bj[N<<1],d[N<<1];
LL ans;
int find(int x){//带权并查集维护异同关系
    if(f[x]==x)return x;
    int t=f[x];
    f[x]=find(f[x]),d[x]=(d[x]+d[t])%2;
    return f[x];
}
LL work(){
    int i,r1,r2,t;LL re=1;
    for(i=1;i<=n+m;++i)f[i]=i,d[i]=0,bj[i]=-1;//bj[i]=-1表示这个集里的元素没有确定,0表示根节点取0,1表示根节点取1;d[i]=1表示i和根节点值不同,等于0表示相同。
    f[n+1]=1,bj[1]=0;
    for(i=1;i<=T;++i){
        if(x[i]==1&&y[i]==1)continue;//特判
        r1=find(x[i]),r2=find(y[i]+n);
        if(r1!=r2){//连接
            f[r1]=r2,d[r1]=(d[y[i]+n]-d[x[i]]+w[i]+4)%2;
            if(bj[r1]!=-1&&bj[r2]!=-1&&bj[r2]!=bj[r1]^d[r1])return 0;
            if(bj[r2]==-1&&bj[r1]!=-1)bj[r2]=bj[r1]^d[r1],bj[r1]=-1;
        }
        else if(d[x[i]]^d[y[i]+n]^w[i]==1)return 0;//判断可行性
    }
    for(i=1;i<=n+m;++i)if(find(i)==i&&bj[i]==-1)re=(re<<1)%mod;//注意取模
    return re;
}
int main(){
    int i,j,flag=-1;
    scanf("%d%d%d",&n,&m,&T);
    for(i=1;i<=T;++i){
        scanf("%d%d%d",&x[i],&y[i],&w[i]);
        if(x[i]==1&&y[i]==1)flag=w[i];
        if(!(x[i]&1)&&!(y[i]&1))w[i]^=1;
    }
    if(flag==-1||flag==0)ans+=work();
    if(flag==-1||flag==1){
        for(i=1;i<=T;++i)//反过来后可以继续当a[1][1]=0
            if(x[i]!=1&&y[i]!=1)w[i]^=1;
        ans+=work();
    }
    ans%=mod//注意取模
    printf("%lld",ans);//我是不是很可爱啊QWQ?
    return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值