题目链接
PT07D - Let us count 1 2 3」PATH
做法
k=1 : 树的
Prufer 序列个数为
nn−2 。
k=2 : 树的
Prufer 序列选一个为根,
nn−1 。
k=3 :
令 fi 为节点数为 i 时无标号有根树的方案数,生成函数为 F(x)=∑fixi 。
考虑到一棵无标号有根树可以看做一个无标号有根森林加一个根组成,而一种大小为 k 的子树贡献用生成函数表示为 ∑xki=(1−xk)−1 ,一共有 fk 种,即 (1−xk)−fk ,则:
F(x)=xk>0∏(1−xk)−fk
两边取 ln 并求导得:
F(x)F′(x)=x1+k>0∑kfk1−xkxk−1xF′(x)=F(x)+(k>0∑kfk1−xkxk−1)F(x)nfn=fn+i>0∑fik∣n−i∑kfk
可以 O(n2) 算出 fn 。
k=4 :
考虑怎么唯一表示一棵树,我们可以用重心表示,所以把根不是重心的都减去。 一个根不为重心,那么有且仅有一个子树大小大于 2n :
ans=fn−i=1∑2nfifn−ians+=f2nf2n−f2n(f2n−1)/2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| #include <bits/stdc++.h> using namespace std; typedef long long ll; int k, n, p; int f[10010], g[10010], h[10010];
int ksm(int x, int y = p - 2) { int w = 1; for(; y; y >>= 1, x = x * x % p) if(y & 1) w = w * x % p; return w; } void solve1() { f[1] = 1; for(int i = 1; i <= n; i++) g[i] = 1; for(int i = 2; i <= n; i++) { f[i] = 0; for(int j = 1; j < i; j++) f[i] = (f[i] + f[j] * g[i - j]) % p; f[i] = f[i] * ksm(i - 1, p - 2) % p; for(int j = i, t = i * f[i] % p; j <= n; j += i) g[j] = (g[j] + t) % p; } } void solve2() { solve1(); int inv2 = (p + 1) / 2; for(int i = 1; i <= n; i++) { int cnt = 0; for(int j = (i - 1) / 2; j; j--) cnt = (cnt + f[j] * f[i - j]) % p; if(!(i & 1)) cnt = (cnt + (ll)f[i / 2] * (f[i / 2] - 1) * inv2 % p) % p; h[i] = (f[i] - cnt + p) % p; } printf("%d\n", h[n]); } int main() { for(; scanf("%d%d%d", &k, &n, &p) != EOF;) { if(k == 1) printf("%d\n", n == 1 ? 1 : ksm(n % p, n - 2)); else if(k == 2) printf("%d\n", ksm(n % p, n - 1)); else if(k == 3) solve1(), printf("%d\n", f[n]); else solve2(); } return 0; }
|