题意
给定一棵树,每次可以删掉一条边在加上另一条边,使它仍是一棵树,求操作至多(k)次可以得到的树的形态数。
判定形态不同:一条边((x, y))在第一棵树中出现,在另一棵树中不出现。
(0 le k le n le 50, n ge 1)
思路
其实有一种 Matrix-Tree 定理+插值思路:见references部分,复杂度(mathcal O(n^4)),不讲了。
结论
(n)个点的森林,含(k)个连通块,每个连通块的大小为(a_i,sum a = n),则在这些连通块间连边的方案数:
证明见references部分。
容斥
显然删去(k)条边会拆除(k+1)个连通块,可以代入计算,但这样有一个问题:可能会把一条删去的原树边连回去,并且可能被计算很多次(一个连通块有多种拆分)。
考虑容斥。限制即需要删掉(k)条边,且不能连回去。每种方案都要选中(k)条边,然后其中的一部分会被删去,这样算出恰好删掉(k)条边的方案。
转化一下。枚举被删掉的边的个数(m),则强制违反了(k-m)个限制,其他限制任意。然后这种方案会被若干种选择(k)条边的方案包含,因为还有(k-m)条没选,还有(n-1-m)条边可以选。写式子:
其中(F(m))为在原树上删去(m)条边后再连(m)条边的方案数。
根据样例,应该把删(0 sim k)的方案都算一遍加起来。
DP
求(F(m))直接 DP 就好了。(n^{k-2})可以直接乘上,后面的求积可以考虑组合意义:每个连通块内任选一个点的方案数。设(f(x, i, 0/1))为以(x)为根的子树,分成(i)个连通块,且根所在的块是否有关键点。分连边/不连边讨论即可。
复杂度
容斥、 DP 均为(mathcal O(n^2))
应该可以分治 NTT ,容斥乱搞优化到(mathcal O(n log^2 n)),但窝不会
References
Code
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
#define File(s) freopen(s".in", "r", stdin), freopen(s".out", "w", stdout)
const int mod = 998244353;
inline int add(int x, int y){return x+y>=mod ? x+y-mod : x+y;}
inline int sub(int x, int y){return x-y<0 ? x-y+mod : x-y;}
inline int mul(int x, int y){return 1LL * x * y % mod;}
inline int power(int x, int y){
int res = 1;
for(; y; y>>=1, x = mul(x, x)) if(y & 1) res = mul(res, x);
return res;
}
inline int inv(int x){return power(x, mod - 2);}
const int Node = 60;
vector<int> G[Node];
int f[Node][Node][2]; // i 为根,j 个连通块,根所在的连通块选了/没选
int n, k;
int C[Node][Node];
int dfs(int x){ // returns min(k + 1, size)
f[x][1][0] = f[x][1][1] = 1;
int s = 1;
static int pf[Node][2];
for(int v : G[x]){
int sv = dfs(v);
int ns = min(s + sv, k + 1);
for(int i=1; i<=s; i++){
pf[i][0] = f[x][i][0], pf[i][1] = f[x][i][1];
f[x][i][0] = f[x][i][1] = 0;
}
for(int i=1; i<=s; i++)
for(int j=1, lim=min(sv, ns - i + 1); j<=lim; j++){
f[x][i+j][0] = add(f[x][i+j][0], mul(pf[i][0], f[v][j][1]));
f[x][i+j][1] = add(f[x][i+j][1], mul(pf[i][1], f[v][j][1]));
f[x][i+j-1][0] = add(f[x][i+j-1][0], mul(pf[i][0], f[v][j][0]));
f[x][i+j-1][1] = add(f[x][i+j-1][1], add(mul(pf[i][0], f[v][j][1]), mul(pf[i][1], f[v][j][0])));
}
s = ns;
}
return s;
}
void preC(int n){
C[0][0] = 1;
for(int i=1; i<=n; i++){
C[i][0] = 1;
for(int j=1; j<=i; j++) C[i][j] = add(C[i-1][j], C[i-1][j-1]);
}
}
int cal(int k){
int res = C[n-1][k];
if(k & 1) res = sub(0, res);
for(int i=1; i<=k; i++){
int F = mul(power(n, i - 1), f[0][i + 1][1]);
if((k ^ i) & 1) res = sub(res, mul(F, C[n - 1 - i][k - i]));
else res = add(res, mul(F, C[n - 1 - i][k - i]));
}
return res;
}
int main(){
scanf("%d%d", &n, &k);
for(int i=1; i<n; i++){
int fa; scanf("%d", &fa);
G[fa].push_back(i);
}
dfs(0);
preC(n + 2);
int res = 0;
for(int i=0; i<=k; i++) res = add(res, cal(i));
printf("%d
", res);
return 0;
}