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;
}

猜你喜欢

转载自blog.csdn.net/weixin_43911947/article/details/113572208
今日推荐