内向树的生成树计数

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

来看一个题

元首把花园分为 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;
	}
}


### 基环内向的数据结构实现与应用 #### 定义 基环内向是一种特殊的图结构,其中包含 \(n\) 个节点和 \(n\) 条边。这种图的特点是一个环连接多个子,而每个节点的出度恰好为 1[^2]。 #### 特性分析 由于每个节点的出度固定为 1,这意味着所有的节点都会沿着唯一的路径最终进入一个环。这一特性使得我们可以将整个图分解成两部分: - **环上的节点**:这些节点构成了核心循环结构。 - **挂载在环上的子**:每条从环出发的分支实际上是一颗普通的。 为了有效解决问题,通常需要先定位环的位置并将其作为特殊处理的核心区域。 --- #### 实现方法 以下是基于深度优先搜索 (DFS) 的一种常见算法用于检测环以及构建基环内向: ```python from collections import defaultdict, deque def find_cycle_and_tree(n, edges): graph = defaultdict(list) # 构建邻接表 for u, v in edges: graph[u].append(v) visited = [0] * n # 记录访问状态: 0=未访问, 1=正在访问, 2=已完全访问 cycle_nodes = [] # 存储构成环的节点 def dfs(node, path): if visited[node] == 1: # 如果当前节点已经在路径中,则发现了一个环 start_of_cycle = node while True: current_node = path.pop() cycle_nodes.append(current_node) if current_node == start_of_cycle: break return True if visited[node] == 2: # 已经完全访问过该节点 return False visited[node] = 1 # 标记为正在访问 path.append(node) for neighbor in graph[node]: if dfs(neighbor, path.copy()): return True visited[node] = 2 # 标记为完全访问 return False # 寻找环 for i in range(n): if not cycle_nodes and visited[i] != 2: if dfs(i, deque([])): break # 将环视为单个节点,其余部分按普通处理 tree_structure = {} for node in range(n): if node not in cycle_nodes: parent = None for nei in graph[node]: if nei in cycle_nodes or nei in tree_structure.values(): parent = nei break if parent is not None: tree_structure[node] = parent return cycle_nodes, tree_structure # 测试输入 edges = [(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)] # 形成一个简单的基环内向 cycle, tree = find_cycle_and_tree(5, edges) print("Cycle Nodes:", cycle) print("Tree Structure:", tree) ``` 上述代码通过 DFS 遍历寻找环,并记录下环中的节点及其对应的子关系。对于非环节点,它们会被映射到其父节点上形成一棵标准的状结构。 --- #### 应用场景 1. **网络拓扑优化**: 在计算机网络设计中,有时需要确保某些设备之间存在冗余链路以提高可靠性,同时又不引入过多复杂性。此时可以通过识别基环内向来简化模型。 2. **任务调度问题**: 当面对一组相互依赖的任务时(即 A 可能依赖于 B 而 B 同样可能间接依赖回 A),利用基环内向可以帮助快速判断是否存在死锁情况。 3. **动态规划加速**: 对于一些涉及大量重复计算的问题,在预处理阶段如果能够提前解析出潜在的环形依赖链条,则可以在后续迭代过程中显著减少不必要的运算量。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值