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));
 } 
posted @ 2022-09-27 17:04  RiverSheep  阅读(21)  评论(0)    收藏  举报