内向树的生成树计数

所谓内向树,就是树上的边是由儿子指向父亲

来看一个题

元首把花园分为 nnn 行 mmm 列的网格。每个格子中都可以放置一个标识,指向上、下、左、右四个方向中的任意一个。元首位于一个格子时,会按照其中标识所指的方向进入周围的格子,或者走出花园(即目的格子不在网格之内)。举个例子 —— 对于下面的放置方式,元首从第 333 行第 222 列的格子开始,会沿着以红色标出的路径走出花园;从第 222 行第 222 列的格子开始,则会在以蓝色标出的环路内不断地行走。

←  

←  

  ↑

  ←

元首已经设计好了大部分格子的标识。元首用字符 LRUD 分别表示指向左、右、上、下四个方向的标识,用字符 . 表示未决定的格子。现在,元首希望将每个 . 替换为 LRUD 中任意一种,使得从花园中的任意一个格子出发,按照上述规则行走,都可以最终走出花园。

你需要编写程序帮助元首计算替换的不同方案数。两个方案不同当且仅当存在一个格子,使得两个方案中该格子内的标识不同。当然,由于答案可能很大,只需给出方案数除以 109+710^9 + 7109+7 所得的余数即可。



对于此题,我们对于边界外建立一个点(n*m+1),网格每个点向指出去的点连边,未知的点,它的连边方式有4种,求生成树的个数

这题的生成树边是单向的,且都由孩子指向父亲,我们管它叫内向树的生成树计数,然后结论就是:另邻接矩阵为G,出度矩阵为D,那么D-G的任意一个n-1行n-1列子矩阵的行列式就是生成树的个数

你要问我为什么是这样的,我这么,怎么可能会嘛。。。

因为这题的点数太大了,如果直接建立矩阵肯定是不行的,但是我们发现对答案有影响的就是指向未知的点,那么就可以只提取出那些关键点(题目保证了关键点个数只有300个),那么我们在这300个点之间建立关系求行列式就好了。

注意的是一开始需要判断,因为已知方向的点可能已经构成环了,我的方法如下:一开始对于那些已知方向的点,用并查集缩点,然后已知方向的点的联通块只能有一个(指向n*m+1)。

#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int MAXN = 305;
const int MOD = 1000000007;
int d[MAXN][MAXN], g[MAXN][MAXN], f[MAXN][MAXN];
int n, m, T, i, j, k, fa[MAXN * MAXN], x[MAXN], y[MAXN], cnt, num[MAXN * MAXN];
int dx[4] = {0, 1, 0, -1}, dy[4] = {-1, 0, 1, 0};
char s[MAXN][MAXN];
inline int find(int x)
{
	if (x != fa[x]) fa[x] = find(fa[x]);
	return fa[x];
}
inline int point(int x, int y)
{
	if (x < 1 || x > n || y < 1 || y > m) return n * m + 1;
	else return (x - 1) * m + y;
}
inline int ksm(int x, int y, int z)
{
	int b = 1;
	while (y)
	{
		if (y & 1) b = 1ll * b * x % z;
		x = 1ll * x * x % z;
		y >>= 1;
	}
	return b;
}
int main()
{
	cin >> T;
	while (T --)
	{
		cin >> n >> m;
		for(i = 1; i <= m * n + 1; i ++)
			fa[i] = i;
		cnt = 0;
		for(i = 1; i <= n; i ++)
			scanf("%s", s[i] + 1);
		for(i = 1; i <= n; i ++)
			for(j = 1; j <= m; j ++)
			{
				if (s[i][j] == '.') {x[++cnt] = i; y[cnt] = j; continue;}
				int xx = find(point(i, j)), yy;
				if (s[i][j] == 'U') yy = find(point(i - 1, j));
				if (s[i][j] == 'D') yy = find(point(i + 1, j));
				if (s[i][j] == 'L') yy = find(point(i, j - 1));
				if (s[i][j] == 'R') yy = find(point(i, j + 1));
				fa[xx] = yy;
			}
		int block = 0;
		for(i = 1; i <= n * m + 1; i ++)
			if (find(i) == i) block ++;
		if (block > cnt + 1) {puts("0"); continue;}
		for(i = 1; i <= cnt; i ++)
			num[point(x[i], y[i])] = i;
		for(i = 1; i <= cnt + 1; i ++)
			for(j = 1; j <= cnt + 1; j ++)
				g[i][j] = d[i][j] = 0;
		num[n * m + 1] = cnt + 1;
		for(i = 1; i <= cnt; i ++)
			for(j = 0; j < 4; j ++)
			{
				int xx = dx[j] + x[i], yy = dy[j] + y[i], ff = find(point(xx, yy));
				if (i != num[ff]) g[i][num[ff]] ++, d[i][i] ++;
			}
		for(i = 1; i <= cnt; i ++)
			for(j = 1; j <= cnt; j ++)
			{
				f[i][j] = d[i][j] - g[i][j];
				if (f[i][j] < 0) f[i][j] += MOD;
			}
		int ans = 1;
		for(i = 1; i <= cnt; i ++)
		{
			int k = 0;
			for(j = i; j <= cnt; j ++)
				if (f[j][i]) {k = j; break;}
			if (!k) continue;
			if (k != i)
			{
				ans *= -1;
				for(j = 1; j <= cnt; j ++)
					swap(f[i][j], f[k][j]);
			}
			for(j = i + 1; j <= cnt; j ++)
			{
				int jb = (long long)f[j][i] * ksm(f[i][i], MOD - 2, MOD) % MOD;
				for(k = 1; k <= cnt; k ++)
					f[j][k] = (f[j][k] - (long long)f[i][k] * jb) % MOD;
			}
		}
		for(i = 1; i <= cnt; i ++)
			ans = 1ll * ans * f[i][i] % MOD;
		if (ans < 0) ans += MOD;
		cout << ans << endl;
	}
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值