1. 程式人生 > >組合數學+樹形dp+第二類striling數--luoguP4827 [國家集訓隊] Crash 的文明世界

組合數學+樹形dp+第二類striling數--luoguP4827 [國家集訓隊] Crash 的文明世界

傳送門

一道組合數學的好題。

首先是 n k 2 nk^2 50 50

分暴力:
x k > ( x + 1 )
k x^k->(x+1)^k
,用二項式定理展開每個節點維護 k + 1 k+1 個數
兩次樹形 d
p dp
分別求子樹的和從父親上傳下來的, f [ u ] [ j ] f[u][j] 表示 u u 節點子樹到它的 j j 次冪和。
程式碼如下:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define N 50005
using namespace std;
const int mod=10007;

inline int rd(){
    int x=0,f=1;char c=' ';
    while(c<'0' || c>'9') f=c=='-'?-1:1,c=getchar();
    while(c<='9' && c>='0') x=x*10+c-'0',c=getchar();
    return x*f;
}

int n,k,cnt,head[N],f[N][151],g[N][151],h[N][151],C[151][151];

struct EDGE{
    int to,nxt;
}edge[N<<1];
inline void add(int x,int y){
    edge[++cnt].to=y; edge[cnt].nxt=head[x]; head[x]=cnt;
}

inline void prework(){
    for(int i=0;i<=k;i++) C[i][0]=1;
    for(int i=1;i<=k;i++)
        for(int j=1;j<=k;j++)
            C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
}

void dfs1(int u,int fa){
    for(int i=head[u];i;i=edge[i].nxt){
        int v=edge[i].to;
        if(v==fa) continue;
        dfs1(v,u);
        for(int j=0;j<=k;j++){
            for(int l=0;l<=j;l++)
                (h[v][j]+=1LL*f[v][l]*C[j][j-l]%mod)%=mod;
            (f[u][j]+=h[v][j])%=mod;
        }
        for(int j=0;j<=k;j++) (++f[u][j])%=mod,(++h[v][j])%=mod; 
    } return;
}

void dfs2(int u,int fa){
    for(int i=head[u];i;i=edge[i].nxt){
        int v=edge[i].to;
        if(v==fa) continue;
        for(int j=0;j<=k;j++){
            for(int l=0;l<=j;l++)
                (g[v][j]+=1LL*g[u][l]*C[j][j-l]%mod)%=mod;
            for(int l=0;l<=j;l++)
                (g[v][j]+=1LL*(f[u][l]-h[v][l]+mod)%mod*C[j][j-l]%mod)%=mod;
        }
        for(int j=0;j<=k;j++) (++g[v][j])%=mod;
        dfs2(v,u);
    } return;
}

int main(){
    n=rd(); k=rd(); 
    for(int i=1;i<n;i++){
        int x=rd(),y=rd();
        add(x,y); add(y,x);
    }
    prework();
    dfs1(1,1); dfs2(1,1);
    for(int i=1;i<=n;i++) printf("%d\n",(f[i][k]+g[i][k])%mod);
    return 0;
}

關於求 k k 次冪的計算,可以轉化成求組合數

首先有一個公式:
n k = i = 0 k S ( k , i ) × i ! × C ( n , i ) n^k=\sum_{i=0}^k S(k,i)\times i!\times C(n,i)
其中 S S 表示第二類 s t r i l i n g striling 數,有遞推公式:
S ( n , i ) = S ( n 1 , i ) × i + S ( n 1 , i 1 ) S(n,i)=S(n-1,i)\times i+S(n-1,i-1)
這樣的話 S S i ! i! 是不變的都可以預處理出來,然後只需要考慮組合數的變化,因為 C ( n , i ) = C ( n 1 , i 1 ) + C ( n 1 , i ) C(n,i)=C(n-1,i-1)+C(n-1,i) ,所以可以維護 f [ u ] [ j ] f[u][j] 表示子樹中 k C ( d i s t ( u , k ) , j ) \sum_k C(dist(u,k),j) ,父親上面的同理,然後樹形 d p dp 維護

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define N 50005
using namespace std;
const int mod=10007;

inline int rd(){
    int x=0,f=1;char c=' ';
    while(c<'0' || c>'9') f=c=='-'?-1:1,c=getchar();
    while(c<='9' && c>='0') x=x*10+c-'0',c=getchar();
    return x*f;
}

int n,k,cnt,head[N],f[N][151],g[N][151],s[151][151],C[N][151],fac[151];

struct EDGE{
    int to,nxt;
}edge[N<<1];
inline void add(int x,int y){
    edge[++cnt].to=y; edge[cnt].nxt=head[x]; head[x]=cnt;
}

inline void prework(){
    for(int i=0;i<=k;i++) C[i][0]=1,s[i][i]=1; s[0][0]=0;
    for(int i=1;i<=N;i++)
        for(int j=1;j<=k;j++)
            C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
    for(int i=2;i<=k;i++)
        for(int j=1;j<=i;j++) s[i][j]=(s[i-1][j]*j%mod+s[i-1][j-1])%mod;
    fac[0]=1;
    for(int i=1;i<=k;i++) fac[i]=1LL*fac[i-1]*i%mod