[USACO18JAN]Lifeguards P 洛谷黑题,单调队列优化DP
传送门:戳我
这道题有两个版本,S和P,S是K等于1的情况,显然可以用线段树水过。
P版本就难了很多,洛谷黑题(NOI/NOI+/CTSC),嘿嘿。
我自己也不是很理解,照着题解写了一遍,然后悟到了一点东西。
dp方程很好想:
dp[i][j]表示处理到第i个元素,已经删掉了j个,但取了第i个。
dp[i][j]=max(dp[k][j-i-k-1])。表示考虑上一个取的区间是k,然后删去的个数就是j-i-k-1。
时间复杂度是O(N*K*K)。显然不满足题目数据(或许卡卡常能过?)
那么考虑单调队列优化
因为我自己也有点蒙圈,怕误导大家,就摘抄了其他巨佬的博客
/* 这段内容摘自luogu用户babingbaboom的博客:https://www.luogu.org/blog/user51357/solution-p4182
考虑优化dp转移
对于第i个区间,设其左端点为l
我们先看一看方程,会发现对dp[i][j]产生贡献的i'-j'=i-1-j
-
对于i之前的那些右端点<=l的区间,它们与i没有重叠部分,所以只要在它们当中取max,再加上第i个区间的长度即可
-
对于那些与i有重叠部分的区间,在当前区间右移的时候,这些dp的贡献会变,但相对大小不会变,所以可以维护一个单调队列,dp[i][j]对应的单调队列的编号为i-1-j,每次先把队头的那些已经跑到左边的区间弹出去(算成1的贡献),然后取队头就是当前的有重叠的状态中的最大答案
然后当前dp值算出来以后要插进对应的单调队列中(编号为i-j的单调队列),如果队尾状态加上与当前状态的右端点差还没有当前状态的dp值大的话,就把它从队尾弹出
*/
代码实现如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<iostream> #include<cstdio> #include<cstring> #include<queue> #include<algorithm> #define maxn 100001 using namespace std; inline void read(int &x) { x=0;int f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} x*=f; } int N,K,dp[maxn][110]; int p[maxn]; struct coww{ int l,r; friend bool operator < (coww a,coww b) { if(a.l==b.l) return a.r>b.r; else return a.l<b.l; } }arr[maxn],cow[maxn]; struct node{ int node,val; }; deque<node>que[maxn]; int main() { read(N);read(K); if (K>=N) { printf("0"); return 0; } for(int i=1;i<=N;i++) { read(arr[i].l); read(arr[i].r); } sort(arr+1,arr+1+N); int right=0,cnt=0; cow[0]=(coww){0,0}; for(int i=1;i<=N;i++) { if(arr[i].r>right) cow[++cnt]=arr[i],right=arr[i].r; else K--; } if(K<0) K=0; N=cnt; for(int i=1;i<=N;i++) { int u=min(K+1,i); for(int j=0;j<u;j++) { int now=i-j-1; while(!que[now].empty()&&cow[que[now].front().node].r<cow[i].l) { node to=que[now].front(); p[now]=max(p[now],to.val+cow[to.node].r); que[now].pop_front(); } dp[i][j]=max(dp[i][j],p[now]+cow[i].r-cow[i].l); if (!que[now].empty()) dp[i][j]=max(dp[i][j],que[now].front().val+cow[i].r); int nowv=dp[i][j]-cow[i].r; now=i-j; while ((!que[now].empty())&&(que[now].back().val<nowv)) que[now].pop_back(); que[now].push_back((node){i,nowv}); } } int ans=0; for (int i=1;i<=N;i++) for (int j=0;j<min(i,K+1);j++) if (j+N-i==K) ans=max(ans,dp[i][j]); printf("%d",ans); }