题意
请求出直径为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+x−1,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;
}