题目描述
牛牛拿到了一个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;
}