[Luogu1848][USACO12OPEN]书架Bookshelf DP+set+决策单调性
题目链接:https://www.luogu.org/problem/show?pid=1848
题目要求书必须按顺序放,其实就是要求是连续的一段。于是就有DP方程$$f[i]=min\{f[j]+max\{h_k\}\}$$其中的k以及j的关系应该满足$$\sum_{k=j+1}^iw_k<=L$$
这样是$O(n^2)$的肯定不行。发现对于一个$h[i]$到前一个比它大的$h[j]$之间,都被$h[i]$所影响这,且这些影响某一段区间的关键点是单调下降的,同时发现$f[j]$总不会比$f[j+1]$更劣,证明显然。
于是我们只需要维护这样一个关键点集就可以从中取得最优答案。观察DP方程中的条件限制,发现前面的点可能会被从前往后舍去,而后面的点可以把之前的点给覆盖掉,想到用单调队列维护。
队首元素的删除用$w$的区间和来维护,队尾元素则直接用新加入的$h[i]$不断合并掉就好了。
然后对于每一个当前合法的关键点,我们都需要记录其对应的答案,每次取最小值。不仅需要插入,同时还需要从中删除掉不合法的答案。我们可以用双堆或者平衡树来做,当然set也是可以的。
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #include<set> 5 using namespace std; 6 typedef long long ll; 7 int inline readint(){ 8 int Num;char ch; 9 while((ch=getchar())<'0'||ch>'9');Num=ch-'0'; 10 while((ch=getchar())>='0'&&ch<='9') Num=Num*10+ch-'0'; 11 return Num; 12 } 13 int n,m; 14 int h[100010],w[100010],la; 15 ll f[100010],sum; 16 int q[100010],head,tail; 17 bool in[100010]; 18 multiset <ll> s; 19 // f[i]=f[j]+max(h[k]) sigma(w[k])<=L k:i->j+1 20 int main(){ 21 n=readint(); 22 m=readint(); 23 la=head=q[0]=1; 24 tail=sum=0; 25 in[1]=true; 26 for(int i=1;i<=n;i++){ 27 h[i]=readint(); 28 w[i]=readint(); 29 sum+=w[i]; 30 while(sum>m){ 31 if(in[la]){ 32 s.erase(f[la-1]+h[la]); 33 in[la]=false; 34 head++; 35 } 36 else h[la]=h[la-1]; 37 sum-=w[la++]; 38 } 39 if(!in[la]){ 40 h[la]=h[la-1]; 41 s.insert(f[la-1]+h[la]); 42 q[--head]=la; 43 in[la]=true; 44 } 45 q[++tail]=i; 46 s.insert(f[i-1]+h[i]); 47 in[i]=true; 48 while(head<=tail&&h[q[tail]]<=h[i]){ 49 s.erase(f[q[tail]-1]+h[q[tail]]); 50 in[q[tail]]=false; 51 tail--; 52 } 53 h[q[++tail]]=h[i]; 54 s.insert(f[q[tail]-1]+h[q[tail]]); 55 in[q[tail]]=true; 56 f[i]=*s.begin(); 57 } 58 printf("%lld\n",f[n]); 59 return 0; 60 }