BZOJ2616: SPOJ PERIODNI【笛卡尔树树形DP】

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/C20181220_xiang_m_y/article/details/102572242

题目描述:

在这里插入图片描述
N≤500,K≤500,h[i] ≤1000000,mod 109+7。

题目分析:

根据图形比较容易想到按照最小值划分区间,那么 h m i n h_{min} *n这一个矩形区域就由这个最小值控制,两边的比 h m i n h_{min} 高的车显然不会互相影响,据此就可以想到一个根据最小值划分然后DP的做法。

划分的过程就是笛卡尔树的形态,所以问题就变成了在笛卡尔树上DP, f [ i ] [ j ] f[i][j] 表示 i i 子树选了 j j 个车的方案数,枚举左右子树以及当前矩形用点转移即可。

Code:

#include<bits/stdc++.h>
#define maxn 505
#define maxh 1000005
using namespace std;
const int mod = 1e9+7;
int n,m,h[maxn],siz[maxn],f[maxn][maxn],tmp[maxn],fac[maxh],inv[maxh];
int lc[maxn],rc[maxn],S[maxn],tp;
inline int C(int n,int m){return 1ll*fac[n]*inv[m]%mod*inv[n-m]%mod;}
void dfs(int u,int dep){
    if(!u) return;
    dfs(lc[u],h[u]),dfs(rc[u],h[u]);//(h[u]-dep)*siz[i]就是当前点的矩形范围
    siz[u]=siz[lc[u]]+siz[rc[u]]+1;
    for(int i=0;i<=siz[u];i++) tmp[i]=0;
    for(int i=siz[lc[u]];i>=0;i--) 
        for(int j=siz[rc[u]];j>=0;j--) 
            tmp[i+j]=(tmp[i+j]+1ll*f[lc[u]][i]*f[rc[u]][j])%mod;//提前将儿子用的总个数为j的方案算出来
    for(int i=0;i<=siz[u];i++)
        for(int j=max(0,i-(h[u]-dep));j<=i;j++)
            f[u][i]=(f[u][i]+1ll*C(siz[u]-j,i-j)*C(h[u]-dep,i-j)%mod*fac[i-j]%mod*tmp[j])%mod;
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        scanf("%d",&h[i]);//建笛卡尔树,维护右链的单调栈,很简洁
        while(tp&&h[i]<=h[S[tp]]) lc[i]=S[tp--];
        tp&&(rc[S[tp]]=i), S[++tp]=i;
    }
    const int N = max(n,*max_element(h+1,h+1+n));
    fac[0]=fac[1]=inv[0]=inv[1]=1;
    for(int i=2;i<=N;i++) fac[i]=1ll*fac[i-1]*i%mod,inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
    for(int i=2;i<=N;i++) inv[i]=1ll*inv[i]*inv[i-1]%mod;
    f[0][0]=1,dfs(S[1],0);
    printf("%d\n",f[S[1]][m]);
}

猜你喜欢

转载自blog.csdn.net/C20181220_xiang_m_y/article/details/102572242