T3 Vladislav and a Great Legend (CF1097G)
T3 Vladislav and a Great Legend (CF1097G)
人生中第一道黑题
对我来说有点过于复杂与难以理解了,可能理解得不是很透彻
花了我一整天,我太弱了
CF传送门
洛谷传送门
题意简述
给你一棵有
对于
再给你一个整数
数据范围
题解
前铺知识
树形依赖背包
[复杂版]{https://blog.csdn.net/wt_cnyali/article/details/76649037}
题意:在一棵有根树上,每个节点都挂着一个物件有
选出一个包含根节点的连通块,使得体积之和不超过背包大小
[简化版]{https://www.cnblogs.com/water-mi/p/9818622.html}
题意:给定一棵有
即每个节点的体积均为1
--下面是解法--
我们可以仿照背包的思想
只不过在这里不是一个整体的背包,而是每个节点上都有一个背包
转移过程相当于把每个子节点当做一个物品,一个一个加进去
其他都与背包一样的
这道题用到的是树形依赖背包的思想
第二类斯特林数
[第二类斯特林数]{https://www.cnblogs.com/gzy-cjoier/p/8426987.html}
定义:第二类斯特林数
求法:
考虑有
如果新开一个,那就是
如果放到已有的盒子里,有
性质:
把k个不同的小球放入
但这里盒子是不同的,因此再乘上
思路
首先,看到要算的东西里有个大大的
而且
可以考虑用第二类斯特林数的那个性质转化
于是
交换和号
噢!
发现其实左边的东西都是可以很快算出来的
因此实际要算的是
于是我们把难以处理的幂换成了组合数
但这样直接去考虑好像和幂没有什么区别
不妨从整体来考虑
考虑它的实际意义,其实就是 对于每个生成树,从
选出
一个一个点来转移
解决了?
做法
NO
这题真正的难点不在于思路,在于接下来的实现
仿照树形依赖背包,设
请注意是 所有点集形成的生成树 (即要求的)而不是 所有生成树
请注意是所有而不是经过
我们是从边的角度来考虑的
也就是说,存在一种特殊情况
只连一棵子树(因为连了多棵子树就合法了),不选根节点而选它连向子节点的边
会被转移,但不会被计入答案
很好理解,就是在前面的点里先选
当然为了好写实际上是这样写的
理解到这可能又会冒出一个问题
这是按边来转移的,所以转移到父节点时,又不
当然其实可以连,也可以不连
所以dp完后还要把
最后一个问题
如何初始化?
因为选了
所以有两种情况
于是有一个东西不能转移
就是初始化时
所以还得减
看起来是
其实是
复杂度证明我也搞不明白就不放上来了
注意事项
· 很难理解,请结合题解与代码,仔细思考,不要着急
AC代码
#include<bits/stdc++.h>
using namespace std;
const long long N=1e5+5,mod=1e9+7;
struct nod{
long long to,nxt;
}e[2*N];
long long head[N],cnt;
void add(long long u,long long v){
e[++cnt].to=v;
e[cnt].nxt=head[u];
head[u]=cnt;
}
long long n,k;
long long dp[N][205],f[N],size[N],ans[N],S[205][205];
void dfs(long long o,long long fa){
size[o]=1,dp[o][0]=2;//初始化,dp[o][0]:选o还是不选o都是0条边(后面会处理这种情况)
for(long long i=head[o];i;i=e[i].nxt){
long long v=e[i].to;
if(v==fa) continue;
dfs(v,o);
for(long long i=0;i<=k;i++) f[i]=0;
for(long long p=0;p<=min(k,size[o]);p++)
for(long long q=0;q<=min(k-p,size[v]);q++)
f[p+q]=(f[p+q]+(dp[o][p]*dp[v][q])%mod)%mod;
//类似背包,f为临时数组避免混用
for(long long j=0;j<=k;j++) dp[o][j]=f[j];
//把答案从f转移到dp[o]
for(long long j=0;j<=k;j++) ans[j]=(ans[j]+mod-dp[v][j])%mod;
//这一步其实可以放到后面,ans要减掉不经过o的情况,这里先减了(为了好写)
size[o]+=size[v];
}
for(long long i=0;i<=k;i++) ans[i]=(ans[i]+dp[o][i])%mod;
//累加答案,前面已经减过多余的答案了
for(long long i=k;i>=1;i--) dp[o][i]=(dp[o][i]+dp[o][i-1])%mod;
//考虑o的父亲,那么显然i条会变成i+1条,于是dp[o]整体往右移一格
dp[o][1]=(dp[o][1]+mod-1)%mod;
//减掉最前面不选o的情况(无法转移至父节点)
}
int main(){
scanf("%lld%lld",&n,&k);
for(long long i=1;i<=n-1;i++){
long long u,v;
scanf("%lld%lld",&u,&v);
add(u,v),add(v,u);
}
dfs(1,0);
S[0][0]=1;
for(long long i=1;i<=k;i++)
for(long long j=1;j<=i;j++)
S[i][j]=(S[i-1][j-1]+(S[i-1][j]*j)%mod)%mod;
//计算第二类斯特林数
long long tmp=1,sum=0;
for(long long i=1;i<=k;i++) tmp=(tmp*i)%mod,sum=(sum+(tmp*S[k][i])%mod*ans[i])%mod;
//计算答案
printf("%lld",sum);
}