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
发现#define一个for真好用233.
#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; }