jzoj6494 勘探(数树,dp)

题意

请求出直径为L的n个点的无标号树有多少种。
n<=200

分析

  • 首先,转化成有根树来数。这题我们可以用直径的中点(不妨假设是一个点,两个点的情况很类似)
  • f [ i ] [ j ] f[i][j] f[i][j]为深度为i,共有j个点的有根树种类数。(我这里想复杂了,事实上设深度<=i的话整个算法会简洁很多,但是本质不会变。
  • 考虑如何构造这颗树不会算重,大小不同的子树自然不会重,问题就是大小相同的部分。这一部分必须一起加入,才能算方案数。
  • 因此,我们按子树的大小来加入。枚举当前要加入的子树大小,然后对所有状态枚举该大小的子树的个数进行转移(注意转移顺序!!!)
  • 这里有一个keypoint是方案数的计算。若某一大小的子树有x个,其种类有k种。方案数可以转化成一个挡板问题,变成算与下一个选的点的距离。求出来是 C ( k + x − 1 , x ) C(k+x-1,x) C(k+x1,x)
  • k很大没法预处理,也不能用O(x)的时间算下降幂。但注意到,当k固定时,x+1的系数可以通过x的系数快速算出来。
  • 按上述做法来加入至少一颗深度为i-1的子树,然后再将其他子树并上去。
  • 最后考虑直径的限制,至少要有两个深度为L/2的儿子,容斥算一下或者再做一个dp都可以。
  • 要多注意各个量的联系,多动一点脑子就可以省下大把的代码+调试时间…
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 210;
int mo, n, l;
ll f[N][N], ml, g[N], ans, jc[N], njc[N], inv[N];
inline void add(ll &x, ll y) {
	x = (x + y) % mo;
}

ll ksm(ll x, ll y) {
	ll ret = 1;
	for(; y; y >>= 1) {
		if (y & 1) ret = ret * x % mo;
		x = x * x % mo;
	}
	return ret;
}

ll xjm(ll x, ll y) {
	ll ret = 1;
	while(y) ret = ret * x % mo, y--;
	return ret;
}

int main() {
	freopen("exploit.in","r",stdin);
	// freopen("exploit.out","w",stdout);
	cin >> n >> l >> mo;
	jc[0] = 1; for(int i = 1; i <= n; i++) jc[i] = jc[i - 1] * i % mo;
	njc[n] = ksm(jc[n], mo - 2);
	for(int i = n - 1; ~i; i--) njc[i] = njc[i + 1] * (i + 1) % mo;
	for(int i = 1; i <= n; i++) inv[i] = njc[i] * jc[i - 1] % mo;

	ml = l / 2;
	f[1][1] = 1; g[0] = 1;
	for(int dep = 2; dep <= ml; dep++) {
		f[dep][1] = 1;
		for(int sz = 1; sz <= n; sz ++) { //子树的大小
			for(int i = n; i; i--) {
				ll xj = f[dep - 1][sz];
				for(int cnt = 1; cnt * sz < i; cnt++) {
					add(f[dep][i], f[dep][i - sz * cnt] * xj % mo * njc[cnt]);
					xj = xj * (f[dep - 1][sz] + cnt) % mo;
				}
			}
		}
		
		f[dep][1] = 0;
		static ll tmp[N]; memcpy(tmp,f[dep],sizeof tmp);
		for(int i = 1; i <= n; i++) if(g[i]) {
			for(int j = n - i; j; j--) {
				add(tmp[i + j], f[dep][j] * g[i]);
			}
		}
		memcpy(f[dep],tmp,sizeof tmp);

		for(int sz = 1; sz <= n; sz ++) { //子树的大小
			for(int i = n; ~i; i--) {
				ll xj = f[dep - 1][sz];
				for(int cnt = 1; cnt * sz <= i; cnt++) {
					add(g[i], g[i - sz * cnt] * xj % mo * njc[cnt]);
					xj = xj * (f[dep - 1][sz] + cnt) % mo;
				}
			}
		}
	}
	static ll h[3][N];
	h[0][1] = 1;
	for(int sz = 1; sz <= n; sz ++) { //子树的大小
		for(int i = n; i; i--) {
			for(int o = 2; ~o; o--) {
				ll xj = f[ml][sz];
				for(int cnt = 1; cnt * sz + i <= n; cnt++) {
					add(h[min(o + cnt,2)][i + sz * cnt], h[o][i] * xj % mo * njc[cnt]);
					xj = xj * (f[ml][sz] + cnt) % mo;
				}
			}
		}
	}
	h[0][1] = 0;
	ll inv2 = ksm(2, mo - 2);
	if (l % 2) {
		static ll s[N];
		for(int i = 1; i <= n; i++) {
			for(int j = 1; j <= i; j++)
				add(s[i], g[i - j] * (h[1][j] + h[2][j]));
		}
		for(int x = 1; x * 2 <= n; x++) {
			int y = n - x;
			if (x != y) add(ans, s[x] * s[y]);
			else {
				add(ans, s[x] * (s[x] - 1) % mo * inv2 + s[x]);
			}
		}
	} else {
		for(int i = 1; i <= n; i++) {
			add(ans, h[2][i] * g[n - i]);
		}
	}
	cout << (ans + mo) % mo << endl;
}

1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 、下4载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 、下4载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合;、 4下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值