[CF995F]Cowmpany Cowmpensation[树形dp+拉格朗日插值]

题意

给你一棵树,你要用不超过 \(D\) 的权值给每个节点赋值,保证一个点的权值不小于其子节点,问有多少种合法的方案。

\(n\leq 3000, D\leq 10^9\)

分析

  • 如果 \(D\) 比较小的话可以考虑状态 \(f_{i,j}\) 表示点 \(i\) 的权值是 \(j\) 的方案总数,\(g_{i,j}\) 表示 \(\sum_\limits{k=1}^jf_{i,j}\) ,转移也比较显然:\(f_{i,j}=\prod g_{son,j}\)

  • 先证明结论:前 \(n\) 个正整数的 \(k\) 次幂之和是 \(k+1\) 次多项式(Imagine大佬果然牛逼)

  • 可以得到,若 \(f\) 是一个 \(k\) 次多项式,且 \(g(x)=\sum_\limits{i=0}^xf(i)\) ,那么 \(g\) 是一个 \(k+1\) 次多项式。 故一个点的 \(g\) 函数的次数是它的子树大小。

  • 算出 \(D\in [0,n]\) 时的值,然后插值一波即可。

  • 因为值连续插值部分可以优化到 \(O(n)\) ,总时间复杂度为 \(O(n^2)\)

这份代码是5个月前的有点丑陋

代码


#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<vector>
#include<cctype>
#include<queue>
#define go(u) for(int i=head[u],v=e[i].to;i;i=e[i].last,v=e[i].to)
using namespace std;
typedef long long LL;
const LL mod=1e9 + 7;
const int N=3004;
int n,edcnt;
int head[N];
LL f[N][N],ans,D;
struct edge{
    int last,to;
    edge(){}edge(int last,int to):last(last),to(to){}
}e[N*2];
void Add(int a,int b){
    e[++edcnt]=edge(head[a],b);head[a]=edcnt;
    e[++edcnt]=edge(head[b],a);head[b]=edcnt;
}
void dfs(int u,int fa){
    for(int i=1;i<=n+1;i++) f[u][i]=1ll;
    go(u)if(v^fa){
      dfs(v,u);
      LL s=0ll;
      for(int j=1;j<=n+1;j++){
        (s+=f[v][j])%=mod;
        f[u][j]=f[u][j]*s%mod;
      }
    }
}
LL Pow(LL a,LL b){
  LL res=1ll;
  for(;b;b>>=1,a=a*a%mod) if(b&1) res=res*a%mod;
  return res;
}
int main(){
   scanf("%d%I64d",&n,&D);
   for(int i=2,x;i<=n;i++){
     scanf("%d",&x);
     Add(i,x);
   }
   dfs(1,0);
   for(int i=2;i<=n+1;i++) (f[1][i]+=f[1][i-1])%=mod;
   for(int i=1;i<=n+1;i++){
     LL fz=f[1][i],fm=1ll;
     for(int j=1;j<=n+1;j++)if(i^j){
        fz=(fz*(D-j)%mod+mod)%mod;
        fm=(fm*(i-j)%mod+mod)%mod;
     }
     (ans+=fz*Pow(fm,mod-2)%mod)%=mod;
   }
   printf("%I64d\n",ans);
   return 0;
}

猜你喜欢

转载自www.cnblogs.com/yqgAKIOI/p/10158781.html
今日推荐