Description
给定一棵 nnn 个点的树,求所有点集的最小斯坦纳树大小的 kkk 次方和。
答案对 109+710^9+7109+7 取模,1≤n≤105,1≤k≤2001 \le n \le 10^5,1 \le k \le 2001≤n≤105,1≤k≤200。
Solution
⌈\lceil⌈ 算法二 ⌋\rfloor⌋ 为本题正解,⌈\lceil⌈ 算法一 ⌋\rfloor⌋ 是个人思考的产物,两部分联系不大,可以直接跳到 ⌈\lceil⌈ 算法二 ⌋\rfloor⌋。
算法一
看到一个和式的 kkk 次方,我们的第一反应是将它拆开。
(∑x∈Sx)k=(k!)∑p,∑x∈Spx=k∏x∈Sxpxpx!\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
我们令每条虚树上的边可以被钦定多次,钦定 xxx 次的代价为 wxx!\frac {w^x} {x!}x!wx,这里 www 为边权,本题中恒等于 111。显然,将所有钦定方案的和乘 k!k!k! 即为答案。
考虑 dp\text{dp}dp。
令 fu,if_{u,i}fu,i 表示,目前看了以 uuu 为根的子树及 uuu 的父边,总共钦定了 iii 次的总贡献。
考虑转移。
-
首先,我们处理边界,即 fu,0f_{u,0}fu,0。
-
接着,将 uuu 的各个儿子 vvv 的 fvf_vfv 合并。具体来说,我们将 fu,if_{u,i}fu,i 与 fv,jf_{v,j}fv,j 合并到 fu,i+j′f'_{u,i+j}fu,i+j′,每次都以 min(sizeu,k)×min(sizev,k)\min(size_u,k) \times \min(size_v,k)min(sizeu,k)×min(sizev,k) 的时间代价进行合并,均摊 O(k)O(k)O(k)。
-
然后,我们考虑 uuu 的父边 (u,fau)(u,fa_u)(u,fau)。令原状态为 fu,if_{u,i}fu,i,若 (u,fau)(u,fa_u)(u,fau) 被钦定了 xxx 次,则可以从 fu,if_{u,i}fu,i 转移到 fu,i+xf_{u,i+x}fu,i+x,转移系数为 1kx\frac {1} {k^x}kx1,可以用 NTT 优化,单次 O(klogk)O(k \log k)O(klogk)。
总复杂度 O(nklogk)O(n k \log k)O(nklogk),考虑优化。
算法二
算法一中,父边可以被钦定很多次,这导致我们很难将 NTT 的 logk\log klogk 去掉。
考虑另一种计算 kkk 次方的算法——斯特林数。
tk=∑i=0k{ki}i!(ti)t^k=\sum_{i=0}^k \begin{Bmatrix} k \\ i \end{Bmatrix} i! {t \choose i}tk=i=0∑k{ki}i!(it)
我们可以 O(k2)O(k^2)O(k2) 地递归预处理出每个 {ki}\begin{Bmatrix} k \\ i \end{Bmatrix}{ki},现在关键在于计算 ∑(ti)\sum {t \choose i}∑(it)。换言之,我们需要求出,在所有点集对应的最小斯坦纳树中任选 kkk 条边的总方案数。
考虑 dp\text{dp}dp。
令 fu,if_{u,i}fu,i 表示,在以 uuu 为根的子树以及 uuu 的父边中,任选 iii 条边的方案数。
考虑转移。
-
首先,我们处理边界,即令 fu,0=2f_{u,0}=2fu,0=2。
-
接着,我们将各个子节点的状态合并到一起,均摊 O(k)O(k)O(k)。
-
然后,我们考虑 uuu 的父边。若它被选,则可以将某个 fu,if_{u,i}fu,i 转移到 fu,i+1f_{u,i+1}fu,i+1;若未被选,则状态轮廓依然是 fu,if_{u,i}fu,i。即,我们执行如下操作:∀i∈[1,min(sizeu,k)]\forall i \in [1,\min(size_u,k)]∀i∈[1,min(sizeu,k)],fu,i:=fu,i+fu,i−1f_{u,i}:=f_{u,i}+f_{u,i-1}fu,i:=fu,i+fu,i−1。
-
注意,fu,1f_{u,1}fu,1 中多计算了一种父边被选,子树为空的情况,根据虚树的定义它显然不合法;从而,我们需要将 fu,1f_{u,1}fu,1 扣去 111。
综上所述,我们通过斯特林数的推导以及 dp\text{dp}dp 方程与转移的推导解决了本题。总时间复杂度 O(nk)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;
}

这篇博客介绍了如何利用斯特林数解决树的最小斯坦纳树问题,通过斯特林数和动态规划相结合的方法,实现了在O(nk)的时间复杂度内求解所有点集最小斯坦纳树大小的kk次方和。算法详细阐述了两种思路,包括边的钦定次数优化和斯特林数的运用,并提供了相应的代码实现。
279

被折叠的 条评论
为什么被折叠?



