首页 > 代码库 > BZOJ4574 [Zjoi2016]线段树

BZOJ4574 [Zjoi2016]线段树

比较厉害的dp.

网上题解都是利用了随机的条件,用了一个$O(n^4)$的dp,这里简单说一下。

用f(x,i,l,r)表示经过前i轮操作,[l,r]的所有数<=x,且l-1和r+1都>x的方案数。

转移:f(x,i,l,r)=f(x,i-1,l,r)*g(l,r)+f(x,i-1,j,r)*(j-1)+f(x,i-1,l,k)*(n-k),j<l,k>r

其中,g(l,r)=l*(l-1)/2+(r-l+1)*(r-l+2)/2+(n-r)*(n-r+1)/2

用个前缀和优化一下转移即可。

设h(x,i)表示最终位置i的数<=x的方案数,h(x,i)=$\sum_{l=1}^i\sum_{r=i}^nf(x,q,l,r)$

ans(i)=$\sum_xx*(h(x,i)-h(x-1,i))$

但是这样的做法不够优秀,有没有不利用随机的特性,严格$O(n^3)$的做法呢?

答案是有的。

其实很简单,观察发现转移的时候第一维是固定的,我们可以直接用dp(i,l,r)表示各种x的贡献和。

把上面ans(i)中的h展开,发现dp(i,l,r)=$\sum_x-f(x,i,l,r)$

转移没有变化,但初始化有变化。

初始化时的dp值怎么计算呢?

发现对于一段极长的区间[l,r],dp(0,l,r)=max(a(l)...a(r))-min(a(l-1),a(r+1)),对于非极长的区间dp(0,l,r)=0

#include <cstdio>
#include <algorithm>
#define F(i,l,r) for(int i=l;i<=r;i++)

const int N=405,p=1e9+7;
int n,q,a[N],f[2][N][N],g[N][N],s1[2][N][N],s2[2][N][N];

int main() {
    scanf("%d%d",&n,&q),a[0]=a[n+1]=1e9+1;
    F(i,1,n) scanf("%d",&a[i]);
    F(i,1,n) {
        int r=0;
        F(j,i,n) {
            g[i][j]=i*(i-1)/2+(n-j)*(n-j+1)/2+(j-i+1)*(j-i+2)/2,r=std::max(r,a[j]);
            if(i==1&&j==n) f[0][i][j]=r;
            else if(a[i-1]>r&&a[j+1]>r) f[0][i][j]=(r-std::min(a[i-1],a[j+1])+p)%p;
        }
    }
    F(i,1,q) {
        int s=i&1,t=(i&1)^1;
        F(j,1,n) for(int k=n;k>=j;k--) s2[t][j][k]=(s2[t][j][k+1]+1LL*f[t][j][k]*(n-k))%p;
        F(j,1,n) F(k,j,n) s1[t][j][k]=(s1[t][j-1][k]+1LL*f[t][j][k]*(j-1))%p,f[s][j][k]=(1LL*f[t][j][k]*g[j][k]+s1[t][j-1][k]+s2[t][j][k+1])%p;
    }
    F(i,1,n) {int a1=0; F(j,1,i) F(k,i,n) a1=(a1+f[q&1][j][k])%p; printf("%d%c",a1," \n"[i==n]);}
    return 0;
}

BZOJ4574 [Zjoi2016]线段树