CF830D Singer House
\(\text{Description}\)
给定一棵深度为\(k (\le 400)\)的满二叉树,每个节点均与其所有祖先连边。
求树中每个节点最多经过一次的不同有向路径数量。
\(\text{Solution}\)
一道需要人类智慧的\(DP\),考虑现在已经求出了树深为\(k - 1\)的答案,那么只需要合并两颗这样的树再加一个根即可,但我们发现难以计算根的方案数,无法计算从子树到根再到子树的方案,除非我们知道子树中两条路径不相交的方案数。
那么\(DP\)的思路就出来了,设\(f_{i,j}\)表示树深为\(i\),选\(j\)条不相交的路径数的方案数。
当不选根为路径时:
\[f_{i,j} = \sum_{x + y = j}f_{i - 1,x} * f_{i - 1,y}
\]
当根自己成为一条路径时:
\[f_{i,j} = \sum_{x + y = j - 1}f_{i - 1,x}*f_{i - 1,y}
\]
当根为一条路径的开始或结束时:
\[f_{i,j} = \sum_{x + y = j}f_{i - 1,x} * f_{i - 1,y} * j * 2
\]
当根连接两条路经时:
\[f_{i,j} = \sum_{x + y = j + 1}f_{i - 1,x} * f_{i - 1,y} * (j + 1) * j
\]
发现有许多无用的状态,可以用\(dfs\)记忆化来实现。
\(\text{Code}\)
#include<cstdio>
#include<cstring>
#define LL long long
using namespace std;
const int P = 1e9 + 7,N = 405;
int n; LL f[N][N];
LL dfs(int x,int k)
{
if (k == 0) return 1;
if (x == 1) return k == 1;
if (f[x][k] != -1) return f[x][k];
LL tmp = 0;
for (int i = 0; i <= k; i++) {
LL b = dfs(x - 1,k - i); if (b == 0) continue;
LL a = dfs(x - 1,i); if (a == 0) continue;
tmp = (tmp + a * b % P) % P;
}
for (int i = 0; i < k; i++) {
LL b = dfs(x - 1,k - i - 1); if (b == 0) continue;
LL a = dfs(x - 1,i); if (a == 0) continue;
tmp = (tmp + a * b % P) % P;
}
for (int i = 0; i <= k; i++)
{
LL b = dfs(x - 1,k - i); if (b == 0) continue;
LL a = dfs(x - 1,i); if (a == 0) continue;
tmp = (tmp + a * b % P * 2LL * (LL)k % P) % P;
}
for (int i = 0; i <= k + 1; i++) {
LL b = dfs(x - 1,k - i + 1); if (b == 0) continue;
LL a = dfs(x - 1,i); if (a == 0) continue;
tmp = (tmp + a * b % P * (LL)(k + 1) * k % P) % P;
}
return f[x][k] = tmp;
}
int main()
{
scanf("%d",&n), memset(f,255,sizeof f);
printf("%lld\n",dfs(n,1));
}