点一成零(并查集,逆元)

题目描述

牛牛拿到了一个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”之后,状态会保留到接下来的询问。具体请参考样例描述。
由于方案数可能过大,请对109+710^{9}+7109+7取模

输入描述:

第一行输入一个 nnn(1≤n≤5001 \leq n \leq 5001≤n≤500)
随后 nnn 行每行有一个 长度为 nnn 的字符串,字符串只可能包含 ‘0’ 或 ‘1’ 字符 ,表示整个方阵
接下来输入一个数 kkk,表示询问的次数。(1≤k≤1051 \leq k \leq 10^{5}1≤k≤105)
随后 kkk 行每行有 2 个整数 xxx 和 yyy 表示将 xxx 行 yyy 列的数字变为 1 的一次修改操作
0≤x,y≤n−10 \leq x, y \leq n -10≤x,y≤n−1

输出描述:

针对每一次变更数字的操作,输出当前的方案数

示例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。

思路

显然如果没有改变的话答案应该是联通块个数n, 1 * 2 * 3 * (n - 1) * n (按点的顺序), 乘以每个联通块的个数。
为便于分析借用一张图片
在这里插入图片描述

在这里插入图片描述
细节见于代码

#include <bits/stdc++.h>

using namespace std;
#define ll long long
const int N = 510;
int n, k;
char a[N][N];
int fa[N * N], siz[N * N];
int dx[] = {1, -1, 0, 0}, dy[] = {0, 0, -1, 1};
ll mod = 1e9 + 7;

int find(int x)
{
	return fa[x] == x ? x : fa[x] = find(fa[x]);
}

void merge(int x, int y)
{
	int fx = find(x), fy = find(y);
	if(fx != fy)
		siz[fx] += siz[fy], fa[fy] = fx;
}

ll qsm(ll a, ll b)
{
	ll ans = 1;
	while(b > 0)
	{
		if(b & 1) ans = ans * a % mod;
		a = a * a % mod;
		b >>= 1;
	}
	return ans % mod;
}

ll ni(int x)
{
	return qsm(x, mod - 2);

}

int main()
{
	scanf("%d", &n);
	for(int i = 1; i <= n; i++)
		scanf(" %s", a[i] + 1);//cout << "(((((";
	n ++;
	for(int i = 0; i <= n * n; i++) fa[i] = i, siz[i] = 1;
	for(int i = 1; i <= n; i++)
	{
		for(int j = 1; j <= n; j++)
		{
			if(a[i][j] == '1')
			{
				for(int k = 0; k < 4; k++)
				{
					int x1 = i + dx[k], y1 = j + dy[k];
					if(a[x1][y1] == '1') merge(i * n + j, x1 * n + y1);
				}
			}
		}
	}
	int cnt = 0;
	ll ans = 1;
	for(int i = 1; i <= n; i++)
		for(int j = 1; j <= n; j++)
			if(fa[i * n + j] == i * n + j && a[i][j] == '1')
				cnt ++, ans = (ans * siz[i * n + j]) % mod;
	for(int i = 1; i <= cnt; i++)
		ans = (ans * i) % mod;
	scanf("%d", &k);
	while(k --)
	{
		int x, y;
		scanf("%d %d", &x, &y);
		x ++, y ++;
		if(a[x][y] == '1')
		{
			printf("%lld\n", ans);
			continue;
		}
		a[x][y] = '1';
		cnt ++;
		ans = ans * cnt % mod;
		for(int i = 0; i < 4; i++)
		{
			int x1 = dx[i] + x, y1 = dy[i] + y;
			if(a[x1][y1] == '1')
			{
				int f1 = find(x * n + y);
				int f2 = find(x1 * n + y1);
				if(f1 != f2)
				{
					ans = ans * ni(cnt) % mod;
					ans = ans * ni(siz[f1]) % mod;
					ans = ans * ni(siz[f2]) % mod;
					ans = ans * (siz[f1] + siz[f2]) % mod;
					cnt --;
					merge(f1, f2);
				}
			}
		}
		printf("%lld\n", ans);

	}
	return 0;

}


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值