[BZOJ5305][Haoi2018]苹果树(组合数学+dp)

这篇博客探讨了如何解决关于苹果树的路径长度之和的问题,涉及到组合数学和动态规划(DP)的方法。博主介绍了如何计算一棵二叉树的所有无序点对的路径长度之和,并详细阐述了动态规划的状态转移方程。最后,给出了简洁的代码实现。
摘要由CSDN通过智能技术生成

Address

洛谷P4492
BZOJ5305
LOJ#2526

Solution

好像只有我一人用这种无脑做法,果然蒟蒻
首先,一棵 N N N 个节点的二叉树中有 N N N 个可以插入新叶子节点的位置。
所以一共会生成 N ! N! N! N N N 个节点的不同的二叉树。
所以,此题从期望题变成了计数题:求生成的所有二叉树的所有无序点对的路径长度之和。
定义状态:
f [ i ] f[i] f[i] 表示 i i i 个节点生成的所有二叉树的所有无序点对的路径长度之和。
g [ i ] g[i] g[i] 表示 i i i 个节点生成的所有二叉树,根到每个点的路径长度之和。
边界:
f [ 0 ] = g [ 0 ] = 0 f[0]=g[0]=0 f[0]=g[0]=0
而一棵二叉树能被生成,当且仅当每个节点的编号都大于其父亲节点编号。
转移先枚举左子树的点数 j j j ,而根必须放最小的编号,所以需要在 i − 1 i-1 i1 个节点中选出 j j j 个节点放进左子树,其余的放进右子树,即 C i − 1 j C_{i-1}^j Ci1j
先讨论 g g g
(1)先考虑根的左子节点到其子树内所有点的距离和加上根的右子节点到其子树内所有点的距离和。当左子树的形态确定时,右子树不管是 ( n − 1 − j ) ! (n-1-j)! (n1j)! 种形态中的哪一种,左子节点到其子树内所有点的距离之和都不变,对于右子树的形态确定时也一样。也就是:
g [ j ] × ( i − 1 − j ) ! + g [ i − 1 − j ] × j ! g[j]\times(i-1-j)!+g[i-1-j]\times j! g[j]×(i1j)!+g[i1j]×j!
(2)对于整棵树的每一种形态,这棵树除了根之外的 i − 1 i-1 i1 个节点走到根都必须经过连接根和其子节点的恰好一条边。所以需要加上的贡献为:
( i − 1 ) × i ! (i-1)\times i! (i1)×i!
所以:
g [ i ] = ( i − 1 ) × i ! + ∑ j = 0 i − 1 { C i − 1 j × ( g [ j ] × ( i − 1 − j ) ! + g [ i − 1 − j ] × j ! ) } g[i]=(i-1)\times i!+\sum_{j=0}^{i-1}\{C_{i-1}^j\times(g[j]\times(i-1-j)!+g[i-1-j]\times j!)\} g[i]=(i1)×i!+j=0i1{Ci1j×(g[j]×(i1j)!+g[i1j]×j!)}
f f f 的转移比较复杂。
(1)和 g g g 一样,把两棵子树的距离之和合并起来:
f [ j ] × ( i − 1 − j ) ! + f [ i − 1 − j ] × j ! f[j]\times(i-1-j)!+f[i-1-j]\times j! f[j]×(i1j)!+f[i1j]×j!
(2)两个端点分别来自两个子树的路径长度之和。
当左子树确定时,右子树有 f [ i − 1 − j ] f[i-1-j] f[i1j] 种确定方案,左子树内的一个点可以和右子树内的 i − 1 − j i-1-j i1j 个点配成路径计入贡献,同时需要考虑:左子树内的每一个点到根时都需要经过连接根的左子节点和其父亲的边。右子树确定时同理。
( g [ j ] + j ! × j ) × ( i − 1 − j ) ! × ( i − 1 − j ) + ( g [ i − 1 − j ] + ( i − 1 − j ) ! × ( i − 1 − j ) ) × j ! × j (g[j]+j!\times j)\times(i-1-j)!\times(i-1-j)+(g[i-1-j]+(i-1-j)!\times(i-1-j))\times j!\times j (g[j]+j!×j)×(i1j)!×(i1j)+(g[i1j]+(i1j)!×(i1j))×j!×j
(3)根到每个点的路径长度,即 g [ i ] g[i] g[i] 在转移完 j j j 后加上。
所以:
f [ i ] = g [ i ] + ∑ j = 0 i − 1 { C i − 1 j × ( f [ j ] × ( i − 1 − j ) ! + f [ i − 1 − j ] × j ! f[i]=g[i]+\sum_{j=0}^{i-1}\{C_{i-1}^j\times(f[j]\times(i-1-j)!+f[i-1-j]\times j! f[i]=g[i]+j=0i1{Ci1j×(f[j]×(i1j)!+f[i1j]×j!
+ ( g [ j ] + j ! × j ) × ( i − 1 − j ) ! × ( i − 1 − j ) + ( g [ i − 1 − j ] + ( i − 1 − j ) ! × ( i − 1 − j ) ) × j ! × j ) } +(g[j]+j!\times j)\times(i-1-j)!\times(i-1-j)+(g[i-1-j]+(i-1-j)!\times(i-1-j))\times j!\times j)\} +(g[j]+j!×j)×(i1j)!×(i1j)+(g[i1j]+(i1j)!×(i1j))×j!×j)}
最后答案 f [ N ] f[N] f[N]

Code

非常短。

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
using namespace std;

const int N = 2005;
int n, ZZQ, fac[N], C[N][N], f[N], g[N];

int main()
{
	int i, j;
	cin >> n >> ZZQ;
	fac[0] = 1;
	For (i, 1, n) fac[i] = 1ll * fac[i - 1] * i % ZZQ;
	For (i, 0, n) C[i][0] = 1;
	For (i, 1, n) For (j, 1, i)
		C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % ZZQ;
	For (i, 1, n)
	{
		For (j, 0, i - 1)
		{
			g[i] = (g[i] + (1ll * C[i - 1][j] *
				(1ll * g[j] * fac[i - 1 - j] % ZZQ +
				1ll * g[i - 1 - j] * fac[j] % ZZQ) % ZZQ % ZZQ) % ZZQ) % ZZQ;
			f[i] = (f[i] + 1ll * C[i - 1][j] *
			((1ll * f[j] * fac[i - 1 - j] + 1ll * f[i - 1 - j] * fac[j]) % ZZQ
			+ (1ll * (g[j] + 1ll * fac[j] * j % ZZQ) *
			fac[i - 1 - j] % ZZQ * (i - 1 - j) % ZZQ
			+ 1ll * (g[i - 1 - j] + 1ll * fac[i - 1 - j] * (i - 1 - j) % ZZQ)
			* fac[j] % ZZQ * j % ZZQ) % ZZQ) % ZZQ) % ZZQ;
		}
		g[i] = (g[i] + 1ll * (i - 1) * fac[i] % ZZQ) % ZZQ;
		f[i] = (f[i] + g[i]) % ZZQ;
	}
	cout << f[n] << endl;
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值