BZOJ.2616.SPOJ PERIODNI(笛卡尔树 树形DP)

BZOJ
SPOJ


直观的想法是构建笛卡尔树(每次取最小值位置划分到两边),在树上DP,这样两个儿子的子树是互不影响的。
\(f[i][j]\)表示第\(i\)个节点,放了\(j\)个车的方案数。
\(v\)\(i\)的一个儿子,对于子树部分的转移,有\[f'[i][j]=\sum_{k\leq j}f[v][j-k]f[i][k]\]

求完子树贡献后,对于\(i\)节点代表的矩形,设高度是\(h\)宽度是\(w\),有\[f'[i][j]=\sum_{k\leq j}f[i][j-k]\cdot k!C_h^kC_{w-j+k}^k\]

然后就ok啦。复杂度是\(O(n^2k)\)。(所以建笛卡尔树可以暴力\(n^2\)
\(O(n)\)建笛卡尔树,就是维护一个单调栈,栈中元素是当前树最右边的一条链。


//9636kb    516ms
#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
#define mod 1000000007
#define Mod(x) x>=mod&&(x-=mod)
#define C(n,m) (1ll*fac[n]*ifac[m]%mod*ifac[n-m]%mod)//n<m
#define gc() getchar()
typedef long long LL;
const int N=504,M=1e6+5;
const LL LIM=5e18;

int m,h[N],sk[N],fa[N],son[N][2],f[N][N],fac[M],ifac[M];//M!!!

inline int read()
{
    int now=0;register char c=gc();
    for(;!isdigit(c);c=gc());
    for(;isdigit(c);now=now*10+c-48,c=gc());
    return now;
}
inline int FP(int x,int k)
{
    int t=1;
    for(; k; k>>=1,x=1ll*x*x%mod)
        if(k&1) t=1ll*t*x%mod;
    return t;
}
int DFS(int x)
{
    static int g[N];
    if(!x) return 0;
//  f[x][0]=1;
//  for(int i=0; i<2; ++i)
//      if(son[x][i])
//      {
//          int v=son[x][i],szv=DFS(v);
//          for(int j=0,v1; j<=sz; ++j)
//              if((v1=f[x][j]))
//                  for(int k=0,v2; k<=szv&&j+k<=m; ++k)
//                      if((v2=f[v][k]))
//                          g[j+k]+=1ll*v1*v2%mod, Mod(g[j+k]);
//          sz+=szv;
//          for(int j=std::min(sz,m); ~j; --j) f[x][j]=g[j], g[j]=0;
//      }
    int ls=son[x][0],rs=son[x][1],a=DFS(ls),b=DFS(rs),m=::m;
    for(int i=0,v1; i<=a; ++i)
        if((v1=f[ls][i]))
            for(int j=0,v2; j<=b&&i+j<=m; ++j)
                if((v2=f[rs][j]))
                    f[x][i+j]+=1ll*v1*v2%mod, Mod(f[x][i+j]);
    int h=::h[x]-::h[fa[x]],w=a+b+1;//sz
    for(int i=std::min(w,m); ~i; --i)
    {
        LL tmp=0;
        for(int j=0; j<=i&&j<=h; ++j) tmp+=1ll*f[x][i-j]*fac[j]%mod*C(h,j)%mod*C(w-i+j,j), tmp>=LIM&&(tmp%=mod);
        f[x][i]=tmp%mod;
    }
    return w;
}

int main()
{
    int n=read(),m=read(),mx=n; ::m=m;//mx=max(hi,n)!
    for(int i=1; i<=n; ++i) mx=std::max(mx,h[i]=read());

    fac[0]=1;
    for(int i=1; i<=mx; ++i) fac[i]=1ll*fac[i-1]*i%mod;
    ifac[mx]=FP(fac[mx],mod-2);
    for(int i=mx; i; --i) ifac[i-1]=1ll*ifac[i]*i%mod;

    int top=0; h[sk[0]=0]=0;//设成-1要改回来啊mdzz(dfs用到h[fa[root]])
    for(int i=1; i<=n; ++i)
    {
        while(h[sk[top]]>h[i])
        {
            int a=sk[top--];
            if(h[sk[top]]>h[i]) son[fa[a]=sk[top]][1]=a;
            else son[fa[a]=i][0]=a;
        }
        sk[++top]=i;
    }
    while(top>1) fa[sk[top]]=sk[top-1], son[sk[top-1]][1]=sk[top], --top;
    int root=sk[1];
    f[0][0]=1, DFS(root), printf("%d\n",f[root][m]);

    return 0;
}

转载于:https://www.cnblogs.com/SovietPower/p/10643326.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值