1. 程式人生 > >[CF995F]Cowmpany Cowmpensation[樹形dp+拉格朗日插值]

[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;
}