• 【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;
    }
    


  • 相关阅读:
    程序命名规则
    CSS样式常用命名参考
    转:数据挖掘资料收集
    javascript占位符
    网站目录,文件夹命名规范
    IIS HTTP 500 内部服务器错误完美解决 IIS 服务器无法加载应用程序 '/LM/W3SVC/1/ROOT'。错误是 '没有注册类别
    人事工资合同管理系统菜单截图
    Vs 正则表达式 查找替换 微软权威参考
    什么是DNS,A记录,子域名,CNAME别名,MX记录,TXT记录,SRV 记录,TTL值
    MT主机控制面板Plesk 使用指南
  • 原文地址:https://www.cnblogs.com/Maxwei-wzj/p/9793737.html
Copyright © 2020-2023  润新知