[CF995F]Cowmpany Cowmpensation[樹形dp+拉格朗日插值]
阿新 • • 發佈:2018-12-21
題意
給你一棵樹,你要用不超過 \(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; }