点一成零

时间限制:C/C++ 2秒,其他语言4秒
空间限制:C/C++ 262144K,其他语言524288K
64bit IO Format: %lld
题目描述
牛牛拿到了一个n*n的方阵,每个格子上面有一个数字:0或1
行和列的编号都是从0到n-1
现在牛牛每次操作可以点击一个写着1的格子,将这个格子所在的1连通块全部变成0。
牛牛想知道,自己有多少种不同的方案,可以把全部格子的1都变成0?
所谓连通块,是指方阵中的两个正方形共用一条边,即(x,y)和以下4个坐标的数是连通的:(x-1,y)、(x+1,y)、(x,y-1)、(x,y+1)
这个问题对于牛牛来说可能太简单了。于是他将这个问题变得更加复杂:
他会选择一个格子,将这个格子上的数字修改成1(如果本来就是1,那么不进行任何改变),再去考虑“点一成零”的方案数。
牛牛想知道,每次“将某个格子修改成1”之后,“把全部格子的1都变成0”的方案数量。
ps:请注意,每次“将某个格子修改成1”之后,状态会保留到接下来的询问。具体请参考样例描述。
由于方案数可能过大,请对1e9+7取模

输入描述:
第一行输入一个 n(1<= n <= 500)
随后 nn 行每行有一个 长度为 nn 的字符串,字符串只可能包含 ‘0’ 或 ‘1’ 字符 ,表示整个方阵
接下来输入一个数 kk,表示询问的次数。(1<= k <=10^5)
随后 k 行每行有 2 个整数 x 和 y 表示将x 行y 列的数字变为 1 的一次修改操作
0≤x,y≤n−1
输出描述:
针对每一次变更数字的操作,输出当前的方案数

输入:

3
100
001
000
3
0 1
1 1
1 2

输出:

4
4
4

思路: 核心:并查集
(并查集妙哉>-<)
先看改题前怎么做,检索矩阵,如果遇到相连的1就归到一个集合中,然后计数。
改题后原来操作不变,改的1可以先看作独立出来的一个连通块,如果周围有1的话再去合并,如果原来是1再去修改就没有意义,直接输出上一次的结果就行。
思路挺简单的,写起来细节挺多,要细心
ps:合并函数里爹和儿子搞反了,调了3小时 =.=


//1->0
//并查集
//改题前,方案数=连通块数!*所有连通块面积乘积和
//改题后,模拟点0成1,再算
#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
int n;
const int p=1e9+7;
int fa[1000005]; //集合根
int siz[1000005]; //每个集合的大小
int dir[][2]={
    
    {
    
    1,0},{
    
    -1,0},{
    
    0,1},{
    
    0,-1}}; //四个方向
char mp[505][505];// 存图
void print()
{
    
    
    for(int i=1;i<=(n+1)*(n+1);i++)
        cout<<siz[i]<<" ";
    cout<<endl;
}
ll qsm(int a,int b)
{
    
    
    ll temp=a,ans=1;
    while( b)
    {
    
    
        if( b & 1) ans=(ans*temp)%p;
        temp=(temp*temp)%p;
        b>>=1;
    }
    return ans%p;
}
ll ni(int x) //除法取模,逆元
{
    
    
    return qsm(x,p-2);
}
int find(int x)
{
    
    
    return fa[x] == x? x: fa[x]=find(fa[x]);
}

void merge(int i, int j)
{
    
    
    int x=find(i);
    int y=find(j);
    if(x!=y) //若不是一个集合的,合并,集合容量增加
        siz[x]+=siz[y],fa[y]=x;
}

int main()
{
    
    
    scanf("%d",&n);
    int cnt=0;
    ll ans=1;

    for(int i=0;i<=(n+1)*(n+1);i++) //集合初始化
        fa[i]=i,siz[i]=1;

    for(int i=1;i<=n;i++)   scanf("%s",mp[i]+1);

//    for(int i=1;i<=n;i++)   printf("%s",mp[i]+1);
    for(int i=1;i<=n;i++)  // 连通块放在一个集合里
    for(int j=1;j<=n;j++)
    {
    
    
        if(mp[i][j]=='1')
        {
    
    
        if(mp[i-1][j]=='1')  merge(i*n+j,(i-1)*n+j);// 用编号表示坐标  i*n+j表示第i行第j个
        if(mp[i+1][j]=='1')  merge(i*n+j,(i+1)*n+j);
        if(mp[i][j-1]=='1')  merge(i*n+j,i*n+j-1);
        if(mp[i][j+1]=='1')  merge(i*n+j,i*n+j+1);
        }
    }
    //计算集合数量
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
    {
    
    
        if( fa[i*n+j]==i*n+j && mp[i][j]=='1') // 如果自己能代表一个连通块集合就算进去
        {
    
    
            cnt++;
            ans=(ans*siz[i*n+j])%p; // 所有连通块面积乘积
        }
    }

    for(int i=1;i<=cnt;i++) ans=(ans*i)%p; // 乘上连通块数目阶乘
    //修改后
    int k;
    scanf("%d",&k);
    while(k--)
    {
    
    
        int x,y;
        scanf("%d %d",&x,&y);
        x++,y++;

        if(mp[x][y]=='1') //原来就是1,直接输出原来答案
        {
    
    
            printf("%lld\n",ans);

            continue;
        }
        mp[x][y]='1';
        cnt++; //先将生成的1,当成独立的连通块处理
        ans=ans*cnt%p;//更新答案

        for(int i=0;i<4;i++)
        {
    
    
            // 注意给的xy 表示行列
            int xx=x+dir[i][0];
            int yy=y+dir[i][1];
            if(mp[xx][yy]=='1')
            {
    
    
            int f1=find(xx*n+yy);
            int f2=find(x*n+y);
            //  如果不是连通块,再合并,因为可能一个连通块四面楚歌的情况,会重复算
            if(f1!=f2)
            {
    
    
                ans=(ans*ni(cnt))%p; //先除掉原来连通块的数据
                ans=(ans*ni(siz[f1]))%p;
                ans=(ans*ni(siz[f2]))%p;
                ans=(ans*(siz[f1]+siz[f2]))%p;
                cnt--;
                merge(f1,f2); //再乘上新的连通块数据,注意cnt不用乘了

            }
            }
        }
        printf("%lld\n",ans);
    }


}

猜你喜欢

转载自blog.csdn.net/m0_53688600/article/details/113654459