从零开始的DP学习之肆
当DP方程中的一部分具有某种单调性时可以用数据结构或者预处理维护来降低复杂度
一开始没有看懂题,尴尬,后来发现题目可以简化成这个样子: 将一个序列划分为若干段,每段长度不超过$L$,求每段中最大值之和的最小值
看起来可以直接二分,然而大概并不可行,不过我们有一个明显的$O(n^2)$的DP思路,和我最近做的一道题思路一样。设$dp[i]$表示以$i$为结尾的最小花费,枚举结尾$i$,在$1->i$中找到所有与$i$间距不超过$L$的$j$来转移,代价就是$(j,i)$中的最大值,可以ST表预处理。即$dp[i]=min(dp[i],dp[j]+max(j,i))(j<=i&&sumlimits_{k=j}^i width[k]<=L)$
考虑如何优化,发现$dp$数组在顺序下是单调不下降的,而当右端点$i$确定时,所有的$j$的最大值在顺序下是单调不上升的,所以我们可以考虑用线段树维护这个最小花费,也就是维护$dp[j]+max(j,i)$的最小值。我们枚举右端点时候相当于移动一个长度不超过$L$的滑动窗口,然后我们对于每个新加入的高度就维护一下它左边这段的最大高度,求出$dp[i]$后再单点修改一下下一个位置就可以了
具体来说是用线段树维护四个值+一个标记:最小值,最大值,dp数组,最小花费。其中最小值是为了在维护最大值的时候二分用的,当找到一个要修改的区间的时候我们二分出要修改的那一块,具体来说就是这块中的最小值小于我们要修改成的值再进去修改。然后说说这只蒟蒻都WA了什么鬼畜的错误,第一次是pushup用串了(雾);第二次是因为调试的时候写的和平时不太一样了,然后线段树release了叶子节点=。=;最后一次发现是区间没卡准(之前都是怎么过的2333)

1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 const int N=100005; 6 long long dp[4*N],mini[4*N],maxx[4*N]; 7 long long laz[4*N],val[4*N]; 8 long long w[N],h[N]; 9 long long n,m,f,tot,ans; 10 void pushup(int nde) 11 { 12 int ls=2*nde,rs=2*nde+1; 13 maxx[nde]=max(maxx[ls],maxx[rs]); 14 mini[nde]=min(mini[ls],mini[rs]); 15 val[nde]=min(val[ls],val[rs]); 16 } 17 void release(int nde) 18 { 19 if(laz[nde]) 20 { 21 int ls=2*nde,rs=2*nde+1; 22 laz[ls]=mini[ls]=maxx[ls]=laz[nde]; 23 laz[rs]=mini[rs]=maxx[rs]=laz[nde]; 24 val[ls]=dp[ls]+maxx[ls],val[rs]=dp[rs]+maxx[rs]; laz[nde]=0; 25 } 26 } 27 void change1(int nde,int l,int r,int nl,int nr,long long task) 28 { 29 if(l>nr||r<nl) return ; 30 if(l!=r) release(nde); int mid=(l+r)/2,ls=2*nde,rs=2*nde+1; 31 if(l>=nl&&r<=nr) 32 { 33 if(task<=maxx[nde]) 34 { 35 if(task>mini[ls]) change1(ls,l,mid,nl,nr,task); 36 if(task>mini[rs]) change1(rs,mid+1,r,nl,nr,task); 37 } 38 else 39 { 40 maxx[nde]=mini[nde]=laz[nde]=task; 41 val[nde]=dp[nde]+maxx[nde]; return ; 42 } 43 } 44 else 45 change1(ls,l,mid,nl,nr,task),change1(rs,mid+1,r,nl,nr,task); 46 pushup(nde); 47 } 48 void change2(int nde,int l,int r,int pos,long long task) 49 { 50 if(l==pos&&r==pos) {dp[nde]=task; return ;} 51 release(nde); int mid=(l+r)/2,ls=2*nde,rs=2*nde+1; 52 if(pos<=mid) change2(ls,l,mid,pos,task); 53 else change2(rs,mid+1,r,pos,task); 54 dp[nde]=min(dp[ls],dp[rs]); 55 } 56 long long query(int nde,int l,int r,int nl,int nr) 57 { 58 if(l>nr||r<nl) 59 return 1e18; 60 else if(l>=nl&&r<=nr) 61 return val[nde]; 62 else 63 { 64 int mid=(l+r)/2,ls=2*nde,rs=2*nde+1; release(nde); 65 return min(query(ls,l,mid,nl,nr),query(rs,mid+1,r,nl,nr)); 66 } 67 } 68 int main () 69 { 70 scanf("%lld%lld",&n,&m),f=1; 71 for(int i=1;i<=n;i++) 72 scanf("%lld%lld",&h[i],&w[i]); 73 for(int i=1;i<=n;i++) 74 { 75 tot+=w[i]; 76 while(tot>m) tot-=w[f++]; 77 change1(1,1,n,1,i,h[i]); 78 ans=query(1,1,n,f,i); 79 if(i!=n) change2(1,1,n,i+1,ans); 80 } 81 printf("%lld",ans); 82 return 0; 83 }