2021牛客寒假演算法基礎集訓營1 D-點一成零
阿新 • • 發佈:2021-02-03
技術標籤:並查集
題目連結:點這裡~
題目大意
- 牛牛拿到了一個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[]
- 那麼方案數就是每個連通塊大小的乘積 *(連通塊個數的階乘),即
- 連通塊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; }