CF1097G 题解

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 1n105,1k200

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!} (xSx)k=(k!)p,xSpx=kxSpx!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=0k{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,i1

  • 注意, 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;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值