题目链接:点这里~
题目大意
- 牛牛拿到了一个n*n的方阵,每个格子上面有一个数字:0或1,行和列的编号都是从0到n-1
- 现在牛牛每次操作可以点击一个写着1的格子,将这个格子所在的1连通块全部变成0。上下左右两个方格是连通的,公用一条边。
- k次询问,每次询问给出x,y,将(x,y)方格变成1,牛牛想知道,每次“将某个格子修改成1”之后,“把全部格子的1都变成0”的方案数量。
- 范围:1<= n <= 500, 1 <= k <= 1e5
思路
- 变0为1,就相当于要将连通块合并,那么就自然而然地想到用并查集来求连通块个数cc和连通块大小siz[]
- 那么方案数就是每个连通块大小的乘积 *(连通块个数的阶乘),即
,令ans等于后者
- 连通块a1与连通块a2合并,就先除去两个连通块大小,之后乘上合并之后的大小,同时连通块个数减一,即 ans = ans * inv(siz[a1]) % mod * (siz[a2]) % mod*(siz[a1]+siz[a2]) % mod,cc--。
ac代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn = 505*505;
const int mod = 1e9 + 7;
char a[505][505];
int dx[] = {0, 0, -1, 1};
int dy[] = {-1, 1, 0, 0};
int pre[maxn], siz[maxn], n;
ll p[maxn];
int find(int a){
if(pre[a] == a) return a;
return pre[a] = find(pre[a]); //路径压缩
}
void merge(int a, int b){
int x = find(a), y = find(b);
if(x == y) return;
if(siz[x] > siz[y]){ //按秩合并,个数小的合并到个数大的连通块
siz[x] += siz[y];
pre[y] = x;
}else{
siz[y] += siz[x];
pre[x] = y;
}
}
int calc(int x, int y){ //化二维为一维
return (x - 1) * n + y;
}
ll _pow(ll a, ll b){ //快速幂
ll ans = 1;
while(b){
if(b & 1) ans = ans * a % mod;
a = a * a % mod;
b >>= 1;
}
return ans;
}
ll inv(ll a){ //逆元
return _pow(a, mod - 2);
}
int main(){
cin >> n;
p[0] = 1;
for(int i = 1; i <= n * n; i ++){
pre[i] = i;
siz[i] = 1;
p[i] = p[i - 1] * i % mod;
}
for(int i = 1; i <= n; i ++){
scanf("%s", a[i] + 1);
}
//上下左右可连通,我们可以只看右和下
for(int i = 1; i <= n; i ++){
for(int j = 1; j <= n; j ++){
if(a[i][j] == '0') continue;
if(j + 1 <= n && a[i][j + 1] == '1') merge(calc(i, j), calc(i, j + 1));
if(i + 1 <= n && a[i + 1][j] == '1') merge(calc(i, j), calc(i + 1, j));
}
}
ll ans = 1, cc = 0; // ans是连通块大小的乘积,cc是连通块个数
for(int i = 1; i <= n; i ++){
for(int j = 1; j <= n; j ++){
int t = calc(i, j);
if(a[i][j] == '1' && find(t) == t){
ans = ans * siz[t] % mod;
cc ++;
}
}
}
int k; cin >> k;
while(k --){
int x, y;
cin >> x >> y;
x ++; y ++;
if(a[x][y] == '1'){
cout << ans * p[cc] % mod << endl;
continue;
}
a[x][y] = '1';
cc ++; //变0为1,多了个连通块
for(int i = 0; i < 4; i ++){
int tx = x + dx[i];
int ty = y + dy[i];
if(tx >= 1 && tx <= n && ty >= 1 && ty <= n && a[tx][ty] == '1'){ //四个方向遍历连通块
int t1 = find(calc(x, y)), t2 = find(calc(tx, ty));
if(t1 != t2){ //合并两个连通块
ans = ans * inv(siz[t1]) % mod * inv(siz[t2]) % mod * (siz[t1] + siz[t2]) % mod;
cc --;
merge(t1, t2);
}
}
}
cout << ans * p[cc] % mod << endl;
}
return 0;
}