题目描述
本题要求计算具有恰好 NNN 条边的两终端串并联网络的数量。串并联网络是通过以下规则递归定义的:
- 一条单独的边是一个串并联网络(基础情况)
- 如果 G1G_1G1 和 G2G_2G2 是串并联网络,那么将它们并联(源点相连,汇点相连)得到的网络也是串并联网络
- 如果 G1G_1G1 和 G2G_2G2 是串并联网络,那么将它们串联(G1G_1G1 的汇点与 G2G_2G2 的源点相连)得到的网络也是串并联网络
重要:题目中将串联顺序交换和并联顺序交换视为等价操作,即我们计算的是同构类的数量。
题目分析
问题转化
这是一个经典的组合计数问题。关键在于将串并联网络转化为树形结构表示:
- 叶子节点:代表原始边
- 内部节点:代表串联或并联操作
- 约束条件:每个内部节点必须至少有两个子节点(因为串联或并联操作需要至少两个组件)
这种转化建立了串并联网络与特定树结构之间的一一对应关系。
关键观察
- 树结构的性质:每个串并联网络对应一棵树,其中叶子节点数为 NNN(边数),内部节点代表操作
- 操作类型:每个内部节点可以是串联或并联,因此每个树结构对应 222 个不同的网络
- 计数方法:我们需要计算具有 NNN 个叶子节点且每个内部节点至少有两个子节点的树的数量,然后乘以 222
解题思路
动态规划方法
我们使用动态规划来解决这个问题:
-
定义状态:
- 设 f[n]f[n]f[n] 表示具有 nnn 个叶子节点且每个内部节点至少有两个子节点的树的数量
- 设 dp[i][j]dp[i][j]dp[i][j] 表示使用最大子树不超过 iii 个叶子节点,总共需要 jjj 个叶子节点的方案数
-
状态转移:
- 基础情况:f[1]=1f[1] = 1f[1]=1(单叶子节点的树只有一种)
- 对于 f[n]f[n]f[n],我们通过 dp[n−1][n]dp[n-1][n]dp[n−1][n] 来计算
- dp[i][j]dp[i][j]dp[i][j] 的转移考虑使用 ppp 棵大小为 iii 的子树:
dp[i][j]=dp[i−1][j]+∑p=1⌊j/i⌋C(f[i]+p−1,p)×dp[i−1][j−p×i] dp[i][j] = dp[i-1][j] + \sum_{p=1}^{\lfloor j/i \rfloor} C(f[i] + p - 1, p) \times dp[i-1][j - p \times i] dp[i][j]=dp[i−1][j]+p=1∑⌊j/i⌋C(f[i]+p−1,p)×dp[i−1][j−p×i]
其中 C(n,k)C(n, k)C(n,k) 是组合数
-
最终答案:对于 NNN 条边的网络,答案为 f[N]×2f[N] \times 2f[N]×2
算法步骤
- 初始化 f[1]=1f[1] = 1f[1]=1
- 对于 nnn 从 222 到 NNN:
- 初始化 dpdpdp 数组
- 计算 dp[i][j]dp[i][j]dp[i][j] 对于所有 1≤i<n1 \leq i < n1≤i<n 和 1≤j≤n1 \leq j \leq n1≤j≤n
- 设置 f[n]=dp[n−1][n]f[n] = dp[n-1][n]f[n]=dp[n−1][n]
- 对于每个查询,输出 f[N]×2f[N] \times 2f[N]×2(特殊情况:N=1N = 1N=1 时输出 111)
复杂度分析
- 时间复杂度:O(N3)O(N^3)O(N3),对于 N≤30N \leq 30N≤30 完全可行
- 空间复杂度:O(N2)O(N^2)O(N2),使用二维 dpdpdp 数组
代码实现
// Series-Parallel Networks
// UVa ID: 10253
// Verdict: Accepted
// Submission Date: 2025-11-05
// UVa Run Time: 0.000s
//
// 版权所有(C)2025,邱秋。metaphysis # yeah dot net
#include <iostream>
#include <vector>
using namespace std;
typedef long long ll;
/**
* @brief 计算组合数 C(n, k)
* @param n 总数
* @param k 选择数
* @return 组合数结果
*/
ll combinations(ll n, ll k) {
if (k > n) return 0;
if (k == 0 || k == n) return 1;
// 利用对称性 C(n,k) = C(n,n-k) 减少计算量
if (k > n - k) k = n - k;
ll result = 1;
for (ll i = 1; i <= k; i++) {
result = result * (n - i + 1) / i;
}
return result;
}
int main() {
const int MAX_N = 30;
vector<ll> treeCount(MAX_N + 1, 0); // treeCount[n]: n个叶子的树的数量
// 基本情况:只有1个叶子的树只有1种(单节点)
treeCount[1] = 1;
// 计算从2到MAX_N个叶子的树的数量
for (int leafCount = 2; leafCount <= MAX_N; leafCount++) {
/**
* dp[maxSubtreeSize][totalLeaves]:
* 使用最大子树大小不超过maxSubtreeSize,总共需要totalLeaves个叶子的方案数
*/
vector<vector<ll>> dp(leafCount + 1, vector<ll>(leafCount + 1, 0));
// 初始化:没有叶子时只有1种方案(空树)
for (int i = 0; i <= leafCount; i++) {
dp[i][0] = 1;
}
// 动态规划计算
for (int maxSubtreeSize = 1; maxSubtreeSize < leafCount; maxSubtreeSize++) {
for (int totalLeaves = 1; totalLeaves <= leafCount; totalLeaves++) {
// 初始值:不使用大小为maxSubtreeSize的子树
dp[maxSubtreeSize][totalLeaves] = dp[maxSubtreeSize - 1][totalLeaves];
// 尝试使用p棵大小为maxSubtreeSize的子树
// 每棵这样的子树有treeCount[maxSubtreeSize]种可能
for (int subtreeCount = 1; subtreeCount * maxSubtreeSize <= totalLeaves; subtreeCount++) {
if (treeCount[maxSubtreeSize] > 0) {
// 从treeCount[maxSubtreeSize]种树中选择subtreeCount棵(可重复选择)
ll waysToChooseSubtrees = combinations(
treeCount[maxSubtreeSize] + subtreeCount - 1, subtreeCount);
// 剩余叶子用更小的子树填充
ll remainingLeaves = totalLeaves - subtreeCount * maxSubtreeSize;
dp[maxSubtreeSize][totalLeaves] +=
waysToChooseSubtrees * dp[maxSubtreeSize - 1][remainingLeaves];
}
}
}
}
// leafCount个叶子的树数量 = 使用最大子树不超过leafCount-1的情况
treeCount[leafCount] = dp[leafCount - 1][leafCount];
}
// 处理输入输出
int edgeCount;
while (cin >> edgeCount && edgeCount != 0) {
if (edgeCount == 1) {
// 特殊情况:只有1条边,只有1种网络
cout << 1 << endl;
} else {
/**
* 最终答案 = 树的数量 × 2
* 因为每个树结构对应两种网络:
* - 根节点为串联操作
* - 根节点为并联操作
*/
cout << treeCount[edgeCount] * 2 << endl;
}
}
return 0;
}
总结
本题通过将串并联网络转化为树形结构,巧妙地利用了动态规划和组合数学来解决问题。关键在于理解网络与树结构之间的对应关系,以及如何通过动态规划高效地计算满足特定条件的树的数量。这种"问题转化+动态规划"的思路在组合计数问题中非常常见且有效。
5634

被折叠的 条评论
为什么被折叠?



