制限時間:C / C ++ 2秒、他の言語4秒
スペース制限:C / C ++ 262144K、他の言語524288K
64ビットIO形式:%lld
タイトルの説明
Niuniuは、各グリッドに番号が付いたn * nの正方行列を取得しました:0または1の
行と列には、0からn-1までの番号が付けられます。
これで、操作するたびに、1のグリッドをクリックして、このグリッド内の1の接続されたすべてのブロックを0に変更できます。
Niuniuは、グリッドのすべての1を0に変更できるスキームがいくつあるか知りたいですか?
いわゆる接続ブロックとは、正方行列の2つの正方形が1つの辺を共有すること、つまり(x、y)であり、次の4つの座標番号が接続されていることを意味します:(x-1、y)、(x + 1、y )、(x、y-1)、(x、y + 1)の問題は
、Niuniuにとって単純すぎる可能性があります。そこで彼はこの問題をより複雑にしました。
彼はグリッドを選択し、このグリッドの数を1に変更し(1の場合は変更なし)、「ゼロへのポイント」プランの数を検討しました。
Niuniuは、「特定のグリッドを1に変更」した後、「すべてのグリッドのすべての1を0に変更する」スキームの数を知りたいと考えています。
ps:「グリッドを1に変更」するたびに、ステータスは次のクエリまで保持されることに注意してください。詳細については、サンプルの説明を参照してください。
プランの数が多すぎる可能性があるため、1e9 +7のモジュラスを使用してください
入力の説明:
最初の行(1 <= n <= 500)にnを入力し、
次にnn行に各行に長さnnの文字列を入力します。文字列には「0」または「1」の文字のみを含めることができます。正方行列全体
次に、クエリの数を表す数値kkを入力します。(1 <= k <= 10 ^ 5)
後続のk行の各行に2つの整数xとyがあります。これは、x行とy列の数を
10≤xに変更する変更操作を表します。 y≤n-1
出力の説明:
番号を変更する操作ごとに、現在の計画番号を出力します
入る:
3
100
001
000
3
0 1
1 1
1 2
出力:
4
4
4
アイデア:コア:
ユニオンチェックセット(チェックセットMiao Zaiに参加>-<)は、
最初に質問を変更する前に行う方法を確認し、行列を取得し、接続された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);
}
}