时间限制: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);
}
}