专栏文章
P12415 题解
P12415题解参与者 1已保存评论 0
文章操作
快速查看文章及其快照的属性,并进行相关操作。
- 当前评论
- 0 条
- 当前快照
- 1 份
- 快照标识符
- @mipexfvb
- 此快照首次捕获于
- 2025/12/03 10:53 3 个月前
- 此快照最后确认于
- 2025/12/03 10:53 3 个月前
注意:本文的“方案数”一词指代的不是同一个东西的方案数,请谨慎辨别。
首先可以发现:我们没必要考虑每一行的结点如何排列,只需要知道每行的结点个数即可。
证明:
只要某一行上的结点数量确定(假设等于 ),那么,通过排列这一行所产生的方案数是固定的,即 (从 个空位中选择 个,让它们变成树的结点)。然后,每一行的结点的排列方式对于后面和前面的点也是没有影响的。对于下面的点:我们计算方案时,只关心下面的点能接在多少个点的后面(即能成为多少个点的儿子),而父节点具体的位置不会对方案数产生影响。对于上面的点:感性理解一下。假设上面的点位置已经固定,我们从下面的点开始构树时,只需要考虑下面的点能成为多少个点的儿子,而在哪一个位置成为儿子节点对总方案数是不会有影响的。
于是可以考虑搜索。由于根很特殊,所以我们把根单独算。枚举根下面的每一行放多少个点,然后分开计算答案。
然后,对于其中的某一层,我们设上面总共有 个点(这意味着这一层的点可以成为多少个点的儿子结点),然后上面那些点构造出来的树总共有 种形态,且我们打算在这一行上面放 个点。
显然,到下一行的时候 会加上 ,我们考虑 如何变化。
我们发现下一行的 (即从第一行到这一行这一棵“部分树”的形态数量)被 个因素影响着:
- 上面所有点的排列方案数(即这一行的 )。
- 这一行所有点的排列方案数(即 ,一开始解释过)。
- 这一行的每个点分别接到哪一个点下面。
因为上面总共有 个点,所以这一行的每一个点有 个选择。根据乘法原理,总方案数就是 个 相乘,即 。
所以下一行的 就是这一行的 乘上 再乘上 。
那么,我们的爆搜就可以搜下去了。我们考虑怎么统计整个问题的答案。
显然,我们最终的树形态数,就是搜完以后的 。但是不要把根给忘掉了。我们的根可以随便放,可以放在任意一行、任意一列。
所以,枚举根放在哪一行,然后把 的初始值设为 即可。
看一下爆搜代码。文中的 实际上是 , 实际上是 。
CPP#include <bits/stdc++.h>
#define int long long
using namespace std;
const int MOD = 1e9 + 7;
const int N = 7000;
int C[N][N];
int cal(int n, int m) {
if (C[n][m]) return C[n][m];
if (m == 1) return C[n][m] = n;
if (n == m || m == 0) return C[n][m] = 1;
if (m > n) return 0;
return C[n][m] = (cal(n - 1, m) + cal(n - 1, m - 1)) % MOD;
}
int Pow(int a, int b) {
int r = 1, x = a, p = b;
while (p) {
if (p & 1) r = r * x % MOD;
x = x * x % MOD, p >>= 1;
}
return r;
}
int n, m, ans;
void dfs(int step, int sum, int way) {
if (step == n + 1) {
ans = (ans + way) % MOD;
} else {
for (int i = 0;i <= m;i++) {
dfs(step + 1, sum + i, cal(m, i) * Pow(sum, i) % MOD * way % MOD);
}
}
}
signed main() {
cin >> n >> m;
for (int i = 1;i <= n;i++) {
dfs(i + 1, 1, m);
}
cout << ans;
return 0;
}
通过文章开篇的证明,不难看出,我们的搜索是无后效性的,即不会互相影响。那么我们只要将 作为函数的返回值,然后记忆化一下即可。
CPP#include <bits/stdc++.h>
#define int long long
using namespace std;
const int MOD = 1e9 + 7;
const int N = 7000;
int C[N][N];
int cal(int n, int m) {
if (C[n][m]) return C[n][m];
if (m == 1) return C[n][m] = n;
if (n == m || m == 0) return C[n][m] = 1;
if (m > n) return 0;
return C[n][m] = (cal(n - 1, m) + cal(n - 1, m - 1)) % MOD;
}
int Pow(int a, int b) {
int r = 1, x = a, p = b;
while (p) {
if (p & 1) r = r * x % MOD;
x = x * x % MOD, p >>= 1;
}
return r;
}
int dp[85][6500], n, m, ans;
int dfs(int step, int sum) {
if (dp[step][sum]) return dp[step][sum];
if (step == n + 1) return dp[step][sum] = 1;
int all = 0;
for (int i = 0;i <= m;i++) {
all = (all + cal(m, i) * Pow(sum, i) % MOD * dfs(step + 1, sum + i)) % MOD;
}
return dp[step][sum] = all;
}
signed main() {
cin >> n >> m;
for (int i = 1;i <= n;i++)
ans = (ans + m * dfs(i + 1, 1) % MOD) % MOD;
cout << ans;
return 0;
}
相关推荐
评论
共 0 条评论,欢迎与作者交流。
正在加载评论...