题目
题目链接:https://codeforces.com/problemset/problem/461/D
给定一个 (n imes n) 的网格,其中 (k) 个位置已经被填充为 O 或 X。
求有多少种填充剩下的位置为 O 或 X 的方式,使得任意位置四周的 O 的数量均为偶数。
答案对 (1000000007) 取模。
(n,kleq 10^5)。
思路
挺巧妙的一道题。下文所有下标均减一,即范围在 ([0,n)) 内。
把 O 看做 (1),X 看做 (0),因为四周的格子异或起来等于 (0),所以有 (a_{i,j}=a_{i-1,j-1}mathrm{ xor }a_{i-1,j+1}mathrm{ xor }a_{i-2,j})。
一直这样不断往上算,画个图可以发现,最终位置 ((x,y)) 的网格会被第一行若干连续奇偶相同的网格贡献。具体的,会被第一行 (jin[|x-y|,min(2n-x-y,x+y)]) 且 (j) 和 (x+y) 奇偶性相同的格子 ((1,j)) 贡献到。
第一行的格子做一下前缀异或和,一个位置 ((x,y)) 被强制填 (p) 其实等价于 (a_{1,|x-y|-2}mathrm{ xor }a_{1,min(2n-x-y,x+y)}=p)。
那么现在题意转化为有若干个关于异或的限制,求异或查分后本质不同的序列数量。那么就在 (|x-y|-2) 和 (1,min(2n-x-y,x+y)) 之间连一条边,然后 dfs 求联通块数量即可。
最终答案要除以 (4),因为 (-1) 和 (-2) 是我们虚构出来的格子,它们任意填数的 (4) 种方案都应该算 (1) 种。
时间复杂度 (O(n+m))。
代码
#include <bits/stdc++.h>
using namespace std;
const int N=100010,MOD=1000000007;
int n,m,tot,ans,col[N],head[N];
char ch[3];
struct edge
{
int next,to,dis;
}e[N*2];
void add(int from,int to,int dis)
{
e[++tot]=(edge){head[from],to,dis};
head[from]=tot;
}
bool dfs(int x,int c)
{
if (col[x] && col[x]==c) return 1;
if (col[x] && col[x]!=c) return 0;
col[x]=c;
for (int i=head[x];~i;i=e[i].next)
if (!dfs(e[i].to,e[i].dis^c)) return 0;
return 1;
}
int main()
{
memset(head,-1,sizeof(head));
scanf("%d%d",&n,&m);
n--;
for (int i=1,x,y;i<=m;i++)
{
scanf("%d%d%s",&x,&y,ch);
x--; y--;
if (x+y>n)
{
add(abs(x-y),2*n-x-y+2,ch[0]=='o');
add(2*n-x-y+2,abs(x-y),ch[0]=='o');
}
else
{
add(abs(x-y),x+y+2,ch[0]=='o');
add(x+y+2,abs(x-y),ch[0]=='o');
}
}
ans=1;
for (int i=0;i<=n+2;i++)
if (!col[i])
{
if (!dfs(i,2)) return printf("0"),0;
ans=ans*2%MOD;
}
printf("%lld",250000002LL*ans%MOD);
return 0;
}