(单调队列优化)洛谷P2627 USACO11OPEN Mowing the Lawn / P3572 POI2014 Little Bird 题解
用来复健单调队列优化 dp 的,对它也加深了一些理解。
1.Mowing the Lawn
题意
在一年前赢得了小镇的最佳草坪比赛后,Farmer John 变得很懒,再也没有修剪过草坪。现在,新一轮的最佳草坪比赛又开始了,Farmer John 希望能够再次夺冠。
然而,Farmer John 的草坪非常脏乱,因此,Farmer John 只能够让他的奶牛来完成这项工作。Farmer John 有 nnn(1≤n≤1051\le n\le 10^51≤n≤105)只排成一排的奶牛,编号为 1∼n1\sim n1∼n。每只奶牛的效率是不同的,奶牛 iii 的效率为 aia_iai(0≤ai≤1090\le a_i\le 10^90≤ai≤109)。
靠近的奶牛们很熟悉,因此,如果 Farmer John安排超过 KKK 只连续的奶牛,那么,这些奶牛就会罢工去开派对。因此,现在 Farmer John 需要你的帮助,计算 FJ 可以得到的最大效率,并且该方案中没有连续的超过 kkk 只奶牛。
思路
设 fi,opf_{i,op}fi,op 表示,第 iii 头奶牛选(op=1op=1op=1)或不选(op=0op=0op=0)的方案数。那么:
fi,0=max(fi−1,0,fi−1,1)fi,1=max{fj,0+si−sj},i−j≤k\begin{matrix}
f_{i,0}=\max(f_{i-1,0},f_{i-1,1})\\
f_{i,1}=\max\{f_{j,0}+s_i-s_j\},i-j\le k
\end{matrix}fi,0=max(fi−1,0,fi−1,1)fi,1=max{fj,0+si−sj},i−j≤k
其中 sis_isi 表示 ∑k=1iak\displaystyle\sum_{k=1}^i a_kk=1∑iak。
对于 fi,1=max{fj,0+si−sj}f_{i,1}=\max\{f_{j,0}+s_i-s_j\}fi,1=max{fj,0+si−sj},sis_isi 提出来得到:fi,1=si+max{fj,0−sj}f_{i,1}=s_i+\max\{f_{j,0}-s_j\}fi,1=si+max{fj,0−sj}。还有个限制等价为 j≥i−kj\ge i-kj≥i−k,发现限制单调递增。因此可以用单调队列维护决策点 jjj。
回忆一下单调队列优化 dp 的基本步骤:弹出越界队首、加入所需元素、获取答案。每次最优决策点取队首,加入新决策点进队尾:
- q[head]<i−kq[head]<i-kq[head]<i−k 就弹出队首;
- f[q[tail]][0]−s[q[tt]]<f[i][0]−s[i]f[q[tail]][0]-s[q[tt]]<f[i][0]-s[i]f[q[tail]][0]−s[q[tt]]<f[i][0]−s[i] 说明队尾不如决策 iii 更优,因此弹出队尾。
答案就是 max(fn,0,fn,1)\max(f_{n,0},f_{n,1})max(fn,0,fn,1)。
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=1e5+9;
ll n,k,a[N];
ll s[N],q[N],f[N][2];
int main()
{scanf("%lld%lld",&n,&k);for(int i=1;i<=n;i++){scanf("%lld",&a[i]);s[i]=s[i-1]+a[i];}ll hh=0,tt=0;for(int i=1;i<=n;i++){f[i][0]=max(f[i-1][0],f[i-1][1]);while(hh<=tt&&q[hh]<i-k)hh++;f[i][1]=f[q[hh]][0]+s[i]-s[q[hh]];while(hh<=tt&&f[q[tt]][0]-s[q[tt]]<f[i][0]-s[i])tt--;q[++tt]=i;}printf("%lld",max(f[n][0],f[n][1]));return 0;
}
2.Little Bird
题意
有 nnn 棵树排成一排,第 iii 棵树的高度是 did_idi。
有 qqq 只鸟要从第 111 棵树到第 nnn 棵树。当第 iii 只鸟在第 jjj 棵树时,它可以飞到第 j+1,j+2,⋯ ,j+kij+1, j+2, \cdots, j+k_ij+1,j+2,⋯,j+ki 棵树。
如果一只鸟飞到一颗高度大于等于当前树的树,那么它的劳累值会增加 111,否则不会。由于这些鸟已经体力不支,所以它们想要最小化劳累值。
1≤n≤1061 \le n \le 10^61≤n≤106,1≤di≤1091 \le d_i \le 10^91≤di≤109,1≤q≤251 \le q \le 251≤q≤25,1≤ki≤n−11 \le k_i \le n - 11≤ki≤n−1。
思路
设 fif_ifi 表示鸟飞到第 iii 棵树的最小劳累值。那么:
fi=min{fj+[aj≤ai]},i−j≤kf_i=\min\{f_j+[a_j\le a_i]\},i-j\le kfi=min{fj+[aj≤ai]},i−j≤k
同样将限制转化为 j≥i−kj\ge i-kj≥i−k,限制单调递增,用单调队列维护决策点。
- q[head]<i−kq[head]<i-kq[head]<i−k 就弹出队首;
- f[q[tail]]+[a[q[tail]]≤ai]>fif[q[tail]]+[a[q[tail]]\le a_i]>f_if[q[tail]]+[a[q[tail]]≤ai]>fi,比更新后的当前值小,直接推出队尾。
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=1e6+9;
ll n,a[N],Q;
ll f[N],q[N];
int main()
{scanf("%lld",&n);for(int i=1;i<=n;i++)scanf("%lld",&a[i]);scanf("%lld",&Q);while(Q--){memset(f,0,sizeof(f));memset(q,0,sizeof(q));ll k;scanf("%lld",&k);ll hh=0,tt=0;q[0]=1;for(int i=2;i<=n;i++){while(hh<=tt&&q[hh]<i-k)hh++;f[i]=f[q[hh]]+(a[q[hh]]<=a[i]);while(hh<=tt&&f[q[tt]]+(a[q[tt]]<=a[i])>f[i])tt--;q[++tt]=i;}printf("%lld\n",f[n]);}return 0;
}