2021牛客寒假算法基础集训营1 D.点一成零

题目链接

题目描述

牛牛拿到了一个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)
随后n行每行有一个 长度为n的字符串,字符串只可能包含 ‘0’ 或 ‘1’ 字符 ,表示整个方阵
接下来输入一个数 k,表示询问的次数。(1≤k≤105)
随后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

说明

将第0行第1列的数变成1之后,方阵变成了这样:
110
001
000
一共有3个1。假设行号作为x轴坐标,列号作为y轴坐标,设坐标为(0,0)的是1号,坐标为(0,1)的是2号,坐标为(1,2)的是3号。
那么共有以下四种方案:
1->3
2->3
3->1
3->2
所以输出4。

将第1行第1列的数变成1之后,方阵变成了这样:
110
011
000
一共有4个1,显然它们是连通的,只要选择任意一个1,那么就全部变成0了,所以是4种方案。

将第1行第2列的数变成1,方阵不会有任何改变:
110
011
000
所以方案数依然为4。

思路

我们可以首先思考无修改情况下的状态数应该怎么求,对于给定的矩阵,我们可以将其分为几个由1组成连通块。由于X个连通块可以任意选择,所以我们拥有X!种选取方法,而对于每一个连通块而言,又将有C(连通块的大小,1)种选取方法,所以答案应当为X!*C(连通块的大小,1)。处理完这一部分后对于修改操作,若修改位置本身即为1,则对于结果不会产生任何影响,否则若增添了新的连通块,则将连通块数量更新,若不同连通块合并则将连通块大小更新用于答案的求取即可,详见代码。(连通块的大小可以使用并查集进行处理,这样可以很方便的维护连通块大小并判断连通块是否需要合并)

代码

#include<bits/stdc++.h>
using namespace std;
const int mod=1e9+7;
const int maxn=1e6+5;
typedef long long ll;
char s[505][505];
int cnt,pre[maxn],size[maxn];
int dir[4][2]={{1,0},{-1,0},{0,1},{0,-1}};
int find(int x)
{
    return x==pre[x]?x:pre[x]=find(pre[x]);
}
void bind(ll x,ll y)
{
    int fx=find(x);
    int fy=find(y);
    if (fx!=fy)
    {
        pre[fy]=fx;
        size[fx]+=size[fy];
    }
}
ll qpow(ll a,ll b)
{
    ll ans = 1;
    while(b)
    {
        if(b&1)
            ans=ans*a%mod;
        a=a*a%mod;
        b>>=1;
    }
    return ans%mod;
}
ll inverse(ll a)
{
    return qpow(a,mod-2);
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    int n;
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>(s[i]+1);
    n++;
    for(int i=0;i<=n*n;i++)
        pre[i]=i,size[i]=1;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++)
        {
            if(s[i][j]=='1')
            {
                if(s[i][j-1]=='1')
                    bind(i*n+j,i*n+j-1);
                if(s[i][j+1] =='1')
                    bind(i*n+j,i*n+j+1);
                if(s[i-1][j]=='1')
                    bind(i*n+j, (i-1)*n+j);
                if(s[i+1][j]=='1')
                    bind(i*n+j, (i+1)*n+j);
            }
        }
    }//合并可连通点,形成连通块
    cnt=0;
    ll ans=1;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++)
        {
            if (pre[i*n+j]==i*n+j&&s[i][j]=='1')
            {
                cnt++;
                ans=(ans*size[i*n+j])%mod;
            }
        }
    }
    for(int i=2;i<=cnt;i++)
        ans=(ans*i)%mod;//处理无修改情况下的结果
    int k;
    cin>>k;
    while(k--)
    {
        int x,y;
        cin>>x>>y;
        x++,y++;
        if(s[x][y]=='1')
        {
            cout<<ans<<'\n';
            continue;
        }
        s[x][y]='1';
        cnt++;
        ans=ans*cnt%mod;//首先假设连通块数量增加
        for(int i=0;i<4;i++)
        {
            int dx=x+dir[i][0];
            int dy=y+dir[i][1];
            if(s[dx][dy]=='1')
            {
                int fx=find(x*n+y);
                int fy=find(dx*n+dy);
                if(fx!=fy)
                {
                    ans=ans*inverse(cnt)%mod;
                    ans=ans*inverse(size[fx])%mod;
                    ans=ans*inverse(size[fy])%mod;
                    ans=ans*(size[fx]+size[fy])%mod;
                    cnt--;
                    bind(fx, fy);
                }//对于改变的点,可以与其余点组成连通块,则连通块数量减少,连通块大小变为两个连通块之和
            }
        }
        cout<<ans<<endl;
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值