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

点一成零

题目描述
牛牛拿到了一个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放在一起
假设连通块数量为n,那么总方案数就是n!∗size(每个连通块大小)
最关键的是处理如果连通块合并的问题
如果没有连通块合并的话可以直接输出答案

AC 代码

#include <iostream>
using namespace std;
int n,k;
const int N=505;
char a[N][N];
int f[N*N];
int size[N*N];
int way[4][2]= {
    
    1,0,-1,0,0,-1,0,1};
const int inf=1e9+7;
int find(int t) {
    
    
	return f[t]==t?t:f[t]=find(f[t]);
}//查找根节点
void mer(int x1,int x2) {
    
    
	int f1=find(x1);
	int f2=find(x2);
	if(f1!=f2)
		size[f1]+=size[f2];
	f[f2]=f1;
}//合并两个连通块,把两个连通块的大小合并成一个
long long qsm(long long a, long long b) {
    
    
	long long ans = 1, t = a;
	while(b) {
    
    
		if (b & 1) ans = ans *t % inf;
		t = t * t % inf;
		b >>= 1;
	}
	return ans % inf;
}//快速幂
long long  ni(int x) {
    
    
	return qsm(x, inf-2);
}//逆元做除法
int main() {
    
    
	cin>>n;
	for(int i=1; i<=n; i++)
		scanf(" %s",a[i]+1);
	n++;//把整个方阵调整至1~n
	for(int i=0; i<=n*n; i++)
{
    
    
	f[i]=i;
	size[i]=1;}
	for(int i=1; i<=n; i++)
		for(int j=1; j<=n; j++) {
    
    
			if(a[i][j]=='1') {
    
    //如果和点到的连通块相等,就合并他们
				if (a[i-1][j] == '1') mer(i*n+j, (i-1)*n+j);
				if (a[i+1][j] == '1') mer(i*n+j, (i+1)*n+j);
				if (a[i][j-1] == '1') mer(i*n+j, i*n+j-1);
				if (a[i][j+1] == '1') mer(i*n+j, i*n+j+1);
			}
		}
	int count=0;
	long long int ans=1;
	for(int i=1; i<=n; i++)
		for(int j=1; j<=n; j++)
			if(f[i*n+j]==i*n+j&&a[i][j]=='1') {
    
    
				count++;//如果是一个单独的连通块,集合数量增加
				ans=(ans*size[i*n+j])%inf;
			}
	for(int i=1; i<=count; i++)
		ans=(ans*i)%inf;
		int t;
	cin>>t;
	while(t--) {
    
    
		int x,y;
		cin>>x>>y;
		x++;
		y++;
		if(a[x][y]=='1') {
    
    
			cout<<ans<<endl;
			continue;//如果本来就是1,直接输出答案
		} else {
    
    
			a[x][y]='1';
			count++;
			ans=ans*count%inf;
			for(int i=0; i<4; i++) {
    
    
				int x1=x+way[i][0];
				int y1=y+way[i][1];
				if(a[x1][y1]=='1') {
    
    //如果不是,就判定
					int f1=find(x*n+y);//如果他的出现导致连通块合并
					int f2=find(x1*n+y1);
					if(f1!=f2) {
    
    /还原ans
						ans = ans *ni(count) % inf;
						ans = ans *ni(size[f1]) % inf;
						ans = ans *ni(size[f2]) % inf;
						ans = ans *(size[f1]+size[f2]) % inf;//重新相乘
						count--;
						mer(f1, f2);
					}
				}
			}
		}
		cout<<ans<<endl;
	}
}

猜你喜欢

转载自blog.csdn.net/qq_34832548/article/details/113732664
今日推荐