题意
给定一棵树,选择一个点集合X,权值F(X)= 联通该集合边的条数
求对所有点集合X,sigma(F(X) k)
n <= 1e5 , k <= 200
题解
一开始以为用斯特林数维护k次方。但是不太会转化
题解给出了一个巧妙的转化:
把k次方看成选一个k维向量,统计每个向量的出现次数。
为了统计方便,先统计有序且不重复出现的
i维向量(1<=i<=k),然后简单dp可以求出把i维向量选成k维的方案数
树形dp的时候要把状态间的转移理清楚。
应该从要求的东西开始,一步步理清条理还是很显然的。但是我推的时候一开始没有想清楚,推了很久
用F(i,j,0…2)表示在子树i,选了j维向量,当前根被选/未选/未选但作为lca的方案数。转移的时候只需要注意第一次选择点的时候贡献是2sz[i]-1,其他直接乘2就好。把边放到它下面的点(其实我一开始把题看错了,看成点的贡献,不过这样转化很方便)
关于复杂度,for的时候与sz取min是O(n * k)的。我们考虑每次小的子树合并给大的,花费k^2的时间size增加k。复杂度 = n / k * k , 而小的子树本身复杂度不超过k ^ 2
这和n ^ 3优化成n ^ 2的树形dp思路一样!
总结:
推式子的题要理清逻辑,一步步把式子推清楚,快速的思考,思维要清晰!可以边写边推,这样对式子更有概念(但是每个部分写之前要推完)
尽量1次AC,只要式子正确完全可以做到
还有这题开longlong恰好mle,细节还是要注意
#include<bits/stdc++.h>
using namespace std;
#define rep(i,l,r) for(register int i = l ; i <= r ; i++)
#define repd(i,r,l) for(register int i = r ; i >= l ; i--)
#define rvc(i,S) for(register int i = 0 ; i < (int)S.size() ; i++)
#define rvcd(i,S) for(register int i = ((int)S.size()) - 1 ; i >= 0 ; i--)
#define fore(i,x)for (register int i = head[x] ; i ; i = e[i].next)
#define forup(i,l,r) for (register int i = l ; i <= r ; i += lowbit(i))
#define fordown(i,id) for (register int i = id ; i ; i -= lowbit(i))
#define pb push_back
#define prev prev_
#define stack stack_
#define mp make_pair
#define fi first
#define se second
#define lowbit(x) (x&(-x))
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
typedef pair<int,int> pr;
const ll inf = 2e18;
const int N = 3e4 + 10;
const int maxn = 100020;
const ll mod = 1e9 + 7;
struct node{
int next,to;
}e[maxn * 2];
int head[maxn],cnt,sz[maxn];
int n,k;
ll inv[maxn],fac[maxn],dp[220][220],sum[220],pow2[maxn];
int f[maxn][220][3],g[220][3];
inline ll power(ll x,ll y){
y = ((y % (mod - 1)) + (mod - 1)) % (mod - 1);
// if ( y < 0 ) return power(power(x,-y),mod - 2);
ll res = 1;
while ( y ){
if ( y & 1 ) res = res * x % mod;
x = x * x % mod;
y >>= 1;
}
return res;
}
inline void up(ll &x,ll y) { x = (x + y) % mod; }
inline void up(int &x,ll y) { x = (x + y) % mod; }
void init(){
inv[0] = fac[0] = 1;
rep(i,1,200) fac[i] = fac[i - 1] * i % mod , inv[i] = power(fac[i],mod - 2);
dp[0][0] = 1;
rep(i,1,k){
rep(j,1,k){
rep(num,1,j){
up(dp[i][j],dp[i - 1][j - num] * inv[num]);
}
}
}
pow2[0] = 1;
rep(i,1,n) pow2[i] = pow2[i - 1] * 2 % mod;
}
void dfs(int x,int fa){
f[x][0][0] = 1;
fore(i,x){
if ( e[i].to == fa ) continue;
dfs(e[i].to,x);
memcpy(g,f[x],sizeof(g));
memset(f[x],0,sizeof(g));
rep(j,0,min(k,sz[x])) rep(l,0,min(k,sz[e[i].to])){
// 0 : x isn't in the set
// 1 : x is chosen
// 2 : x isn't chosen but is lca
if ( j + l > k ) break;
if ( !j || !l ){
up(f[x][j + l][0],g[j][0] * ((ll)f[e[i].to][l][0] + f[e[i].to][l][1] + f[e[i].to][l][2]));
up(f[x][j + l][2],g[j][2] * ((ll)f[e[i].to][l][0] + f[e[i].to][l][1] + f[e[i].to][l][2]));
}
else{
up(f[x][j + l][2],g[j][0] * ((ll)f[e[i].to][l][0] + f[e[i].to][l][1] + f[e[i].to][l][2]));
up(f[x][j + l][2],g[j][2] * ((ll)f[e[i].to][l][0] + f[e[i].to][l][1] + f[e[i].to][l][2]));
}
}
sz[x] += sz[e[i].to];
}
sz[x]++;
up(f[x][1][1],pow2[sz[x]] - 1); //第一次选点的时候统计子树内贡献
//f[x][0][0] = f[x][0][2] = 0;
rep(i,0,min(k,sz[x])){
if ( i ) up(f[x][i + 1][1],2ll * (f[x][i][0] + f[x][i][2]));
f[x][i][0] = f[x][i][0] * 2 % mod;
f[x][i][2] = f[x][i][2] * 2 % mod;
}
}
void solve(){
ll ans = 0;
rep(i,1,k){
// cout<<"i: "<<i<<endl;
if ( i == 1 ){
rep(j,1,n) up(sum[i],(pow2[n - sz[j]] - 1) * (pow2[sz[j]] - 1));
}
else{
rep(j,1,n){
up(sum[i],f[j][i][2] * pow2[n - sz[j]] + f[j][i][1] * (pow2[n - sz[j]] - 1));
// cout<<f[j][i][2]<<" "<<f[j][i][1]<<endl;
}
}
up(ans,sum[i] * dp[i][k] % mod * fac[k]);
}
cout<<ans<<endl;
}
inline void adde(int x,int y){
e[++cnt].to = y;
e[cnt].next = head[x];
head[x] = cnt;
}
int main(){
scanf("%d %d",&n,&k);
rep(i,1,n - 1){
int x,y;
scanf("%d %d",&x,&y);
adde(x,y) , adde(y,x);
}
init();
dfs(1,0);
solve();
}