Description
给定一棵 n n n 个点的树,求所有点集的最小斯坦纳树大小的 k k k 次方和。
答案对 1 0 9 + 7 10^9+7 109+7 取模, 1 ≤ n ≤ 1 0 5 , 1 ≤ k ≤ 200 1 \le n \le 10^5,1 \le k \le 200 1≤n≤105,1≤k≤200。
Solution
⌈ \lceil ⌈ 算法二 ⌋ \rfloor ⌋ 为本题正解, ⌈ \lceil ⌈ 算法一 ⌋ \rfloor ⌋ 是个人思考的产物,两部分联系不大,可以直接跳到 ⌈ \lceil ⌈ 算法二 ⌋ \rfloor ⌋。
算法一
看到一个和式的 k k k 次方,我们的第一反应是将它拆开。
( ∑ x ∈ S x ) k = ( k ! ) ∑ p , ∑ x ∈ S p x = k ∏ x ∈ S x p x p x ! \left(\sum_{x \in S} x \right)^k=(k!)\sum_{p,\sum_{x \in S} p_x=k} \prod_{x \in S} \frac {x^{p_x}} {p_x!} (x∈S∑x)k=(k!)p,∑x∈Spx=k∑x∈S∏px!xpx
我们令每条虚树上的边可以被钦定多次,钦定 x x x 次的代价为 w x x ! \frac {w^x} {x!} x!wx,这里 w w w 为边权,本题中恒等于 1 1 1。显然,将所有钦定方案的和乘 k ! k! k! 即为答案。
考虑 dp \text{dp} dp。
令 f u , i f_{u,i} fu,i 表示,目前看了以 u u u 为根的子树及 u u u 的父边,总共钦定了 i i i 次的总贡献。
考虑转移。
-
首先,我们处理边界,即 f u , 0 f_{u,0} fu,0。
-
接着,将 u u u 的各个儿子 v v v 的 f v f_v fv 合并。具体来说,我们将 f u , i f_{u,i} fu,i 与 f v , j f_{v,j} fv,j 合并到 f u , i + j ′ f'_{u,i+j} fu,i+j′,每次都以 min ( s i z e u , k ) × min ( s i z e v , k ) \min(size_u,k) \times \min(size_v,k) min(sizeu,k)×min(sizev,k) 的时间代价进行合并,均摊 O ( k ) O(k) O(k)。
-
然后,我们考虑 u u u 的父边 ( u , f a u ) (u,fa_u) (u,fau)。令原状态为 f u , i f_{u,i} fu,i,若 ( u , f a u ) (u,fa_u) (u,fau) 被钦定了 x x x 次,则可以从 f u , i f_{u,i} fu,i 转移到 f u , i + x f_{u,i+x} fu,i+x,转移系数为 1 k x \frac {1} {k^x} kx1,可以用 NTT 优化,单次 O ( k log k ) O(k \log k) O(klogk)。
总复杂度 O ( n k log k ) O(n k \log k) O(nklogk),考虑优化。
算法二
算法一中,父边可以被钦定很多次,这导致我们很难将 NTT 的 log k \log k logk 去掉。
考虑另一种计算 k k k 次方的算法——斯特林数。
t k = ∑ i = 0 k { k i } i ! ( t i ) t^k=\sum_{i=0}^k \begin{Bmatrix} k \\ i \end{Bmatrix} i! {t \choose i} tk=i=0∑k{ki}i!(it)
我们可以 O ( k 2 ) O(k^2) O(k2) 地递归预处理出每个 { k i } \begin{Bmatrix} k \\ i \end{Bmatrix} {ki},现在关键在于计算 ∑ ( t i ) \sum {t \choose i} ∑(it)。换言之,我们需要求出,在所有点集对应的最小斯坦纳树中任选 k k k 条边的总方案数。
考虑 dp \text{dp} dp。
令 f u , i f_{u,i} fu,i 表示,在以 u u u 为根的子树以及 u u u 的父边中,任选 i i i 条边的方案数。
考虑转移。
-
首先,我们处理边界,即令 f u , 0 = 2 f_{u,0}=2 fu,0=2。
-
接着,我们将各个子节点的状态合并到一起,均摊 O ( k ) O(k) O(k)。
-
然后,我们考虑 u u u 的父边。若它被选,则可以将某个 f u , i f_{u,i} fu,i 转移到 f u , i + 1 f_{u,i+1} fu,i+1;若未被选,则状态轮廓依然是 f u , i f_{u,i} fu,i。即,我们执行如下操作: ∀ i ∈ [ 1 , min ( s i z e u , k ) ] \forall i \in [1,\min(size_u,k)] ∀i∈[1,min(sizeu,k)], f u , i : = f u , i + f u , i − 1 f_{u,i}:=f_{u,i}+f_{u,i-1} fu,i:=fu,i+fu,i−1。
-
注意, f u , 1 f_{u,1} fu,1 中多计算了一种父边被选,子树为空的情况,根据虚树的定义它显然不合法;从而,我们需要将 f u , 1 f_{u,1} fu,1 扣去 1 1 1。
综上所述,我们通过斯特林数的推导以及 dp \text{dp} dp 方程与转移的推导解决了本题。总时间复杂度 O ( n k ) O(nk) O(nk)。
Code
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=100005,maxk=205,mod=1e9+7;
int read(){
int s=0,w=1;char ch=getchar();
while (ch<'0'||ch>'9'){if (ch=='-') w=-w;ch=getchar();}
while (ch>='0'&&ch<='9'){s=(s<<1)+(s<<3)+(ch^'0');ch=getchar();}
return s*w;
}
int n,k,res,cnt;
int st[maxk][maxk],head[maxn],f[maxn][maxk],ans[maxk],jc[maxk],siz[maxn],tmp[maxk];
struct edge{int nxt,to;}e[maxn<<1];
void add_edge(int u,int v){
cnt++;
e[cnt].to=v,e[cnt].nxt=head[u],head[u]=cnt;
}
void dfs(int now,int fath){
f[now][0]=2,siz[now]=1;
for (int i=head[now];i;i=e[i].nxt){
int y=e[i].to;
if (y==fath) continue;
dfs(y,now);
for (int j=0;j<=k;j++) tmp[j]=0;
for (int j=0;j<=min(siz[now],k);j++){
for (int w=0;w<=min(siz[y],k-j);w++)
tmp[j+w]=(tmp[j+w]+f[now][j]*f[y][w])%mod;
}
for (int j=0;j<=k;j++){
f[now][j]=tmp[j];
ans[j]=(ans[j]+mod-f[y][j])%mod;
}
siz[now]+=siz[y];
}
for (int i=0;i<=k;i++) ans[i]=(ans[i]+f[now][i])%mod;
for (int i=min(siz[now],k);i>=1;i--) f[now][i]=(f[now][i]+f[now][i-1])%mod;
f[now][1]=(f[now][1]+mod-1)%mod;
}
signed main(){
n=read(),k=read();
for (int i=1;i<n;i++){
int u=read(),v=read();
add_edge(u,v),add_edge(v,u);
}
st[0][0]=1,jc[0]=1;
for (int i=1;i<=k;i++) jc[i]=(jc[i-1]*i)%mod;
for (int i=1;i<=k;i++){
for (int j=1;j<=k;j++) st[i][j]=(st[i-1][j-1]+st[i-1][j]*j)%mod;
}
dfs(1,0);
for (int i=1;i<=k;i++){
int cur=(st[k][i]*jc[i])%mod;
cur=(cur*ans[i])%mod;
res=(res+cur)%mod;
}
cout<<res<<endl;
return 0;
}