1. 程式人生 > 其它 >2021牛客寒假演算法基礎集訓營1 D-點一成零

2021牛客寒假演算法基礎集訓營1 D-點一成零

技術標籤:並查集

題目連結:點這裡~

題目大意

  • 牛牛拿到了一個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[]
  • 那麼方案數就是每個連通塊大小的乘積 *(連通塊個數的階乘),即cc! * \prod siz[i]
    ,令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;
}