点一成零
题目描述
牛牛拿到了一个n*n的方阵,每个格子上面有一个数字:0或1
行和列的编号都是从0到n-1
现在牛牛每次操作可以点击一个写着1的格子,将这个格子所在的1连通块全部变成0。
牛牛想知道,自己有多少种不同的方案,可以把全部格子的1都变成0?
所谓连通块,是指方阵中的两个正方形共用一条边,即(x,y)和以下4个坐标的数是连通的:(x-1,y)、(x+1,y)、(x,y-1)、(x,y+1)
这个问题对于牛牛来说可能太简单了。于是他将这个问题变得更加复杂:
他会选择一个格子,将这个格子上的数字修改成1(如果本来就是1,那么不进行任何改变),再去考虑“点一成零”的方案数。
牛牛想知道,每次“将某个格子修改成1”之后,“把全部格子的1都变成0”的方案数量。
ps:请注意,每次“将某个格子修改成1”之后,状态会保留到接下来的询问。具体请参考样例描述。
由于方案数可能过大,请对1e9+7取模
输入描述:
第一行输入一个 n(1<= n <= 500)
随后 nn 行每行有一个 长度为 nn 的字符串,字符串只可能包含 ‘0’ 或 ‘1’ 字符 ,表示整个方阵
接下来输入一个数 kk,表示询问的次数。(1<= k <=10^5)
随后 k 行每行有 2 个整数 x 和 y 表示将x 行y 列的数字变为 1 的一次修改操作
0≤x,y≤n−1
输出描述:
针对每一次变更数字的操作,输出当前的方案数
输入案例:
3
100
001
000
3
0 1
1 1
1 2
输出案例:
4
4
4
解题思路
利用查并集把在一块的1放在一起
假设连通块数量为n,那么总方案数就是n!∗size(每个连通块大小)
最关键的是处理如果连通块合并的问题
如果没有连通块合并的话可以直接输出答案
AC 代码
#include <iostream>
using namespace std;
int n,k;
const int N=505;
char a[N][N];
int f[N*N];
int size[N*N];
int way[4][2]= {
1,0,-1,0,0,-1,0,1};
const int inf=1e9+7;
int find(int t) {
return f[t]==t?t:f[t]=find(f[t]);
}//查找根节点
void mer(int x1,int x2) {
int f1=find(x1);
int f2=find(x2);
if(f1!=f2)
size[f1]+=size[f2];
f[f2]=f1;
}//合并两个连通块,把两个连通块的大小合并成一个
long long qsm(long long a, long long b) {
long long ans = 1, t = a;
while(b) {
if (b & 1) ans = ans *t % inf;
t = t * t % inf;
b >>= 1;
}
return ans % inf;
}//快速幂
long long ni(int x) {
return qsm(x, inf-2);
}//逆元做除法
int main() {
cin>>n;
for(int i=1; i<=n; i++)
scanf(" %s",a[i]+1);
n++;//把整个方阵调整至1~n
for(int i=0; i<=n*n; i++)
{
f[i]=i;
size[i]=1;}
for(int i=1; i<=n; i++)
for(int j=1; j<=n; j++) {
if(a[i][j]=='1') {
//如果和点到的连通块相等,就合并他们
if (a[i-1][j] == '1') mer(i*n+j, (i-1)*n+j);
if (a[i+1][j] == '1') mer(i*n+j, (i+1)*n+j);
if (a[i][j-1] == '1') mer(i*n+j, i*n+j-1);
if (a[i][j+1] == '1') mer(i*n+j, i*n+j+1);
}
}
int count=0;
long long int ans=1;
for(int i=1; i<=n; i++)
for(int j=1; j<=n; j++)
if(f[i*n+j]==i*n+j&&a[i][j]=='1') {
count++;//如果是一个单独的连通块,集合数量增加
ans=(ans*size[i*n+j])%inf;
}
for(int i=1; i<=count; i++)
ans=(ans*i)%inf;
int t;
cin>>t;
while(t--) {
int x,y;
cin>>x>>y;
x++;
y++;
if(a[x][y]=='1') {
cout<<ans<<endl;
continue;//如果本来就是1,直接输出答案
} else {
a[x][y]='1';
count++;
ans=ans*count%inf;
for(int i=0; i<4; i++) {
int x1=x+way[i][0];
int y1=y+way[i][1];
if(a[x1][y1]=='1') {
//如果不是,就判定
int f1=find(x*n+y);//如果他的出现导致连通块合并
int f2=find(x1*n+y1);
if(f1!=f2) {
/还原ans
ans = ans *ni(count) % inf;
ans = ans *ni(size[f1]) % inf;
ans = ans *ni(size[f2]) % inf;
ans = ans *(size[f1]+size[f2]) % inf;//重新相乘
count--;
mer(f1, f2);
}
}
}
}
cout<<ans<<endl;
}
}