题目大意
给出一个 n ∗ n n*n n∗n的01矩阵,选择某个连通块的其中一个1就能将这个块中所有的1都变成0,求每次将某个位置变为1后再将所有连通块都变为0的方案数(变为1后的位置会影响到下面的查询)。
解题思路
首先如果没有修改操作,也就是找到所有的连通块的个数 c n t cnt cnt以及每个连通块中1的数量 x i x_i xi,答案就是 c n t ! ∗ ∏ i = 1 c n t x i cnt!*\prod_{i=1}^{cnt}x_i cnt!∗∏i=1cntxi。
然后考虑修改,假设修改位置已经是1那么就是上次的答案;如果是0,那么当前位置就变为 1 1 1,这时答案就会发生变化:若四周四个位置没有连通块那么连通块个数就会变大,否则连通块个数要么不变要么变小。
如果我们每次都搜索出所有的连通块一定超时,那么使用并查集维护连通块,二维的并查集实际上就是将每个位置转化为一维的坐标。当0变为1后搜索四个位置的连通块都连接到这个新的1上。先拿上次的答案除以上次的连通块个数,除以更新位置四周旧的每个连通块1的个数,然后乘上新的连通块个数乘上新的连通块的1的个数就是这次修改的答案。除法使用逆元即可,有一个易错的细节下面代码有注释。
//
// Created by Happig on 2021/2/7.
//
#include <bits/stdc++.h>
#include <unordered_map>
using namespace std;
#define ENDL "\n"
#define lowbit(x) (x & (-x))
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef pair<double, double> pdd;
const double eps = 1e-8;
const double pi = acos(-1.0);
const int inf = 0x3f3f3f3f;
const double dinf = 1e300;
const ll INF = 1e18;
const int Mod = 1e9 + 7;
const int maxn = 1e5 + 10;
const int dx[] = {0, 0, 1, -1}, dy[] = {1, -1, 0, 0};
int a[505][505], f[505 * 505], d[505 * 505], n, st;
bool vis[505][505];
ll fac[505 * 505];
set<int> res;
int Find(int x) {
return f[x] == x ? x : f[x] = Find(f[x]);
}
void Union(int x, int y) {
int fx = Find(x), fy = Find(y);
if (fx != fy) {
f[fx] = fy;
d[fy] += d[fx];
}
}
bool check(int x, int y) {
if (x < 0 || y < 0 || x >= n || y >= n) return 0;
return 1;
}
void dfs(int x, int y) {
if (!check(x, y) || !a[x][y] || vis[x][y]) return;
Union(x * n + y, st);
vis[x][y] = 1;
for (int i = 0, r, c; i < 4; i++) {
r = dx[i] + x, c = dy[i] + y;
dfs(r, c);
}
}
ll qkp(ll x, ll n, ll p) {
ll ans = 1;
x %= p;
while (n) {
if (n & 1) ans = ans * x % p;
x = x * x % p;
n >>= 1;
}
return ans;
}
ll inv(ll x, ll p) {
return qkp(x, p - 2, p);
}
int main() {
//freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int k, x, y;
string s;
cin >> n;
fac[0] = d[0] = 1, f[0] = 0;
for (int i = 1; i <= n * n; i++) f[i] = i, d[i] = 1, fac[i] = fac[i - 1] * i % Mod;
for (int i = 0; i < n; i++) {
cin >> s;
for (int j = 0; j < n; j++) {
a[i][j] = s[j] - '0';
}
}
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
if (a[i][j] && !vis[i][j]) {
st = i * n + j;
res.insert(st);
dfs(i, j);
}
int cnt = res.size();
ll ans = fac[cnt];
for (auto i: res) {
ans = ans * d[i] % Mod;
}
cin >> k;
while (k--) {
cin >> x >> y;
if (a[x][y]) {
cout << ans << ENDL;
} else {
a[x][y] = 1;
res.clear();
for (int i = 0, r, c; i < 4; i++) {
r = x + dx[i], c = y + dy[i];
if (!check(r, c)) continue;
if (a[r][c]) {
res.insert(Find(r * n + c));
//一开始在这里Union(r * n + c, x * n + y);,然后wa了debug很久
// 实际上如果四周都是1且都是同一个连通块那么就把新的连通块也放入旧的集合中了
}
}
ans = ans * inv(fac[cnt], Mod) % Mod;
cnt -= (int) res.size() - 1;
for (auto i: res) {
Union(i, x * n + y);
ans = ans * inv(d[i], Mod) % Mod;
}
ans = ans * fac[cnt] % Mod;
ans = ans * d[Find(x * n + y)] % Mod;
cout << ans << ENDL;
}
}
return 0;
}