YbtOJ-森林之和【dp】
阿新 • • 發佈:2022-02-07
正題
題目大意
一個節點的權值定義為它度數的平方,求所有\(n\)個點的有標號森林的所有節點權值和。
\(1\leq n,T\leq 5\times 10^3\)
解題思路
首先因為所有節點本質相同,所以我們可以只考慮一個節點所有情況下的權值和。
然後考慮這個平方和怎麼做,我們可以視為指定一個節點連出兩顆子樹的方案(可以相同)。
那麼考慮這個怎麼做,首先我們需要處理出\(n\)個節點有根樹和無根樹的陣列\(r,f\)。
然後我們要考慮怎麼統計除了指定子樹以外的方案,首先我們需要處理出\(n\)個點的森林個數\(s_n\)。
我們可以考慮每次列舉新加入的樹的大小,但是要指定這個節點編號最小的節點編號必須是\(1\)
然後還要算上非指定的子樹中和\(1\)號點聯通的其他節點的方案,那麼有
\[g_n=\sum_{i=0}^ns_ir_{i+1}\binom{n}{i} \]至於指定子樹的話,我們列舉指定子樹的大小轉移就好了。
時間複雜度:\(O(n^2+T)\)
code
#include<cstdio> #include<cstring> #include<algorithm> #define ll long long using namespace std; const ll N=5100; ll T,P,C[N][N],f[N],r[N],g[N],s[N],d[N],ans[N]; ll power(ll x,ll b){ ll ans=1; while(b){ if(b&1)ans=ans*x%P; x=x*x%P;b>>=1; } return ans; } signed main() { freopen("forest.in","r",stdin); freopen("forest.out","w",stdout); scanf("%lld%lld",&T,&P); C[0][0]=1; for(ll i=1;i<N;i++) for(ll j=0;j<=i;j++) C[i][j]=(C[i-1][j]+(j?C[i-1][j-1]:0))%P; f[0]=f[1]=r[0]=r[1]=g[0]=s[0]=1; for(ll i=2;i<N;i++) f[i]=power(i,i-2),r[i]=f[i]*i%P; for(ll i=1;i<N;i++) for(ll j=0;j<i;j++) (s[i]+=s[j]*f[i-j]%P*C[i-1][j]%P)%=P; for(ll i=1;i<N;i++){ for(ll j=0;j<=i;j++) (g[i]+=s[j]*f[i-j+1]%P*C[i][j]%P)%=P; for(ll j=1;j<i;j++) (ans[i]+=r[j]*g[i-j-1]%P*C[i-1][j]%P)%=P; } for(ll i=1;i<N;i++){ d[i]=ans[i+1]; // for(ll j=1;j<=i;j++) // (d[i]+=r[j]*g[i-j]%P*C[i][j]%P)%=P; for(ll j=1;j<i-1;j++) (ans[i]+=d[j]*r[i-j-1]%P*C[i-1][j]%P)%=P; } while(T--){ ll n;scanf("%lld",&n); printf("%lld\n",ans[n]*n%P); } return 0; }