【APIO2011T1】方格染色-并查集+位运算推导

测试地址:方格染色

做法:我们设红色为1,蓝色为0,ci,j为第i行第j列格子的颜色,那么根据条件:每个2*2的区域中1的数量为奇数,涉及奇偶性我们想到异或,所以将条件改写为:

对于任意i>1,j>1有:ci-1,j-1 xor ci-1,j xor ci,j-1 xor ci,j = 1(1)

进一步推导,对于任意i,j有:c1,1 xor c1,j xor ci,1 xor ci,j = “i,j同为偶数”事件的真值(2)

从上面可以知道每一个格子都与第一行和第一列的格子的颜色有关,可以证明每一组第一行和第一列的取值对应着一组解。但由于题目中有已填色格子的存在,导致有一些其他的约束条件,所以我们不能直接计算答案。我们考虑这种做法:最外层枚举c1,1,然后依次处理每个条件,根据(2),由于c1,1和ci,j已确定,那么c1,j和ci,1的关系(相同或不同)就可以确定了。设两个点颜色相同则关系值为0,不同则关系值为1,得到计算关系值的方法:

关系值 = c1,j xor ci,1 = (c1,1 xor c1,j xor ci,1 xor ci,j) xor (c1,1 xor ci,j) = (i为偶数 and j为偶数) xor c1,1 xor ci,j

这里的关系就是约束条件了,我们可以用并查集来维护每个点与父亲之间的关系值,添加约束条件时先检查两个点是否属于一个集合,如果不属于则将两个集合合并(注意关系值的维护!),如果属于同一个集合就检查两个点的关系值是否和已经算出来的相同,如果不相同说明无解。令x为最后不同的集合数,可以知道最后解的个数为2^(x-1),之所以要-1是因为c1,1已确定导致包含这个点的集合中所有点的颜色都已确定,仅有一种取值情况,而其他的集合都有两种取值情况。

最后把c1,1为0或1的两种情况答案数加起来就是最终的答案。注意如果c1,1在约束条件中已经确定,那么就只用计算已经确定的这种情况。

注释:我将第i行第1列的格子存储为点i,第1行第i列的格子存储为点i+n,注意点1和点n+1都是指第1行第1列的格子,所以在处理之前就应该把这两个点所在集合合并。我用f[i]表示点i的父亲,rank[i]表示点i与其父亲的关系值,然后就按照上述方法做即可。

以下是本人代码:

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#define mod 1000000000
#define ll long long
using namespace std;
int n,m,k,f[2000010],rank[2000010];
int op[1000010][3],c=-1;
ll ans=0;

int find(int x)
{
  int r=x,i=x,t=0,j;
  while(f[r]!=r)
  {
    t=(t+rank[r])%2;
    r=f[r];
  }
  t=(t-rank[i]+2)%2;
  while(i!=r)
  {
    j=f[i];
	rank[i]=(rank[i]+t)%2;
	f[i]=r;
	i=j;
	t=(t-rank[i]+2)%2;
  }
  return r;
}

void merge(int x,int y,int p)
{
  int fx=find(x),fy=find(y);
  rank[fx]=(rank[y]+p-rank[x]+2)%2;
  f[fx]=fy;
}

void work(bool p)
{
  int tot=n+m-1;
  for(int i=1;i<=k;i++)
  {
    if (op[i][0]==1&&op[i][1]==1) continue;
    int r=(op[i][0]%2==0&&op[i][1]%2==0)^(p^op[i][2]);
    if (find(op[i][0])!=find(op[i][1]+n))
	{
	  merge(op[i][0],op[i][1]+n,r);
	  tot--;
	}
	else if (r^(rank[op[i][0]]^rank[op[i][1]+n])) return;
  }
  int s=1;
  tot--;
  while(tot--) s=(s<<1)%mod;
  ans=(ans+s)%mod;
}

int main()
{
  scanf("%d%d%d",&n,&m,&k);
  for(int i=1;i<=k;i++)
  {
    scanf("%d%d%d",&op[i][0],&op[i][1],&op[i][2]);
	if (op[i][0]==1&&op[i][1]==1)
	{
	  if (c==-1) c=op[i][2];
      else c=2;
	}
  }
  
  ans=0;
  if (c==-1||c==0)
  {
    for(int i=1;i<=n+m;i++)
      f[i]=i,rank[i]=0;
	f[n+1]=1;
    work(0);
  }
  if (c==-1||c==1)
  {
    for(int i=1;i<=n+m;i++)
	  f[i]=i,rank[i]=0;
	f[n+1]=1;
	work(1);
  }
  
  printf("%lld",ans);
  
  return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值