python方格染色_Luogu P3631 [APIO2011]方格染色

思路

对于这道题,我们从题目里可以知道,蓝色代表的方块为0,红色代表的方块为1。按照题目要求,如果换一种说法,那就是对于一个2*2的方格,其中1的个数必定有奇数个,这样的话,每个方格里的所

有数的异或和必定为1(0^0=0 , 1^0=1 , 1^1=0)。那么对于每一个格子\(a(i,j)\),都有:a(i,j)^a(i+1,j)^a(i,j+1)^a(i+1,j+1)=1

我们钦定S(i , j)为每一个以点(i , j)为右下角的方格的异或和。然后把范围扩大,设想对于一个i*j的方格,它的异或和会是什么呢?(这个部分需要自己推一下,时间关系我就不给例子了)

我们多举几个例子,就可以发现对于任意一个以点(i , j)为右下角的矩阵,它的区域内所有可能的22的方格的异或和再异或起来,得到的就是以(i , j)为右下角的矩阵中包括的所有22矩阵的异

或和的异或和。而这个异或和,根据a^a=0的规定,这个矩阵的S(i , j)仅仅与点(1 , 1)、(i , 1)、(1 , j)、(i , j)有关。

再以此类推,我们就可以发现对于任意一个点,它的值只与(1 , 1)、(i , 1)、(j , 1)这几个点有关。因此我们就可以知道,只要我们明确了表格中第一列和第一行的内容,那表格中其他点

的内容也就是唯一的。

因此,对于a(1 , 1)^a(i , 1)^(1 , j)^a(i , j),当i,j均为偶数时值为1(此时\((i-1) \times (j-1)\)为奇数,对于S函数的异或前缀和为1),否则为0(此时(i-1)*(j-1)为偶数,对于

S函数的异或前缀和为0)。即为a(1 , 1)^a(i , 1)^(1 , j)^a(i , j)=0/1(这里0/1表示值为0或者1)。

这样的话,我们可以考虑枚举a(1 , 1)的值,根据a(i , j)和a(1 , 1)的异同关系(就是对于上一段中说的i , j的奇偶性的问题),就可以确定a(i , 1)和a(1 , j)的异同关系,

即a(i , 1)^a(1 , j)=0/1^0/1^a(i , j)。

那么到现在我们还没有提到过关于并查集的一个字(而这又是一道并查集的题目)。由于我们可以通过枚举a(1 , 1)确定出a(i , 1)和a(j , 1)的异同关系,我们就应该有所启发。

考虑一下食物链那道题。同样都是维护不同的集合,维护集合的关系,这样想这两个题就是极其类似的。我们使用并查集维护有关系的点a(i , 1)和a(j , 1)的异同关系,将他们之间连边。

最后考虑有多少个集合,答案即为2的集合数量减1次方。有人可能会有疑问,为什么不是2的集合个数次方呢?这是因为(1 , 1)这个点自己就是一个集合,但是对答案并没有贡献,所以要减一。

Code

#include

#include

#include

#include

#define MAXN 400005

#define MOD 1000000000

typedef long long ll;

int n, m, k;

int f[MAXN], c[MAXN];//c是指该点与其父节点的关系,相同或不同

int ans;

struct node{

int r, c, opt;

} inp[MAXN];//存储读入的信息

inline int read(void){

int f = 1, x = 0;char ch;

do{ch = getchar();if(ch=='-')f = -1;} while (ch < '0' || ch > '9');

do{ x = x * 10 + ch - '0';ch = getchar();} while (ch >= '0' && ch <= '9');

return f * x;

}

inline int get_father(int x) {

if (f[x] == x) return x;

int f1 = get_father(f[x]);

c[x] = c[x] ^ c[f[x]];//这里不要忘了维护该点与父节点的关系

return f[x] = f1;

}

inline int quick_pow(int a,int b){

int res = 1;

while(b){

if(b&1)

res = 1ll * res * a % MOD;

a = 1ll * a * a % MOD;

b >>= 1;

}

return res;

}

inline void calc(int flag) {

for (int i = 1; i <= n + m; ++i)

f[i] = i, c[i] = 0;//初始化要到n+m,因为维护的是第一行和第一列的点,共n+m个

if (flag == 1){//如果点(1,1)是1,那么对除该点外所有数处理(详见题解里推的式子)

for (int i = 1; i <= k; ++i) {

if (inp[i].r > 1 && inp[i].c > 1)

inp[i].opt ^= 1;//0^1=1,1^1=0,0^0=0

}

}

f[n + 1] = 1;//我们把第一列的数从1到m编号,把第一行的点从n+1到n+n编号,那么(1,1)点的编号会有1和n+1两个,这里直接连边

for (int i = 1; i <= k; ++i) {//枚举每一个给定的点

if (inp[i].r == 1 && inp[i].c == 1)

continue;//如果是(1,1),忽略即可

int f1 = get_father(inp[i].r), f2 = get_father(inp[i].c + n);//取父亲

if (f1 == f2) {//如果已经在同一集合里了,则判断是否满足关系式

if ((c[inp[i].r] ^ c[inp[i].c + n]) != inp[i].opt)

return;//不满足直接返回

}

else {

f[f1] = f2;//如果不在同一集合,那么连边

c[f1] = c[inp[i].c + n] ^ c[inp[i].r] ^ inp[i].opt;//更新异同关系

}

}

int cnt = 0;

for (int i = 1; i <= n + m;++i)

if(f[i]==i)++cnt;

ans = (1ll * ans + quick_pow(2, cnt - 1)) % MOD;

}

int main() {

n = read(), m = read();

k = read();

int ck = -1;

for (int i = 1; i <= k; ++i) {

inp[i].r = read(), inp[i].c = read();

inp[i].opt = read();

if ((!(inp[i].r & 1)) && (!(inp[i].c & 1)))

inp[i].opt ^= 1;//如果i,j同是偶数,异或1

if (inp[i].r == 1 && inp[i].c == 1)

ck = inp[i].opt;//若给定了(1,1)的值,存储下来

}

if (ck == -1 || ck == 0) calc(0);//枚举

if (ck == -1 || ck == 1) calc(1);

printf("%lld\n", ans);//输出

return 0;

}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值