什么是单调队列?
举个浅显的例子:
滑动窗口:给出n个数,要求从每个位置开始到其后的k位中的最大值。
显然这题可以用RMQ问题的做法解,但用单调队列效率最高。
我们维护一个队列,这个队列有两个参数,一个是位置编号,一个是其值,这个队列具有“编号单调增,值单调减”的特点,因为如果i>j,且a[i]>a[j],显然只要i和j在一个窗口中,j就不会是最大值,我们可以把j删掉(对于冗余状态的清除就是优化).然后在队首取出最大值,若最大值已经不在当前窗口,则删去,取下一个。因为每个值最多进队一次,出队一次,所以效率达到O(n)下界。
那么则么用单调队列优化DP呢?
对于一些转移方程可以写成DP(i)=F(j)+a[i],F(j)是一些只于j有关的数,那么我们可以很直接的用单调队列,但如果是下面这道题呢?
给出n个数,要求分成m组,使得每一组的最大值减去最小值的平方之和最小。
分析:
首先对a进行排序,很直接的有dp[i][j]=min{dp[i-1][k]+(a[j]-a[k+1])^2},其中i表示当前分成i组,j表示当前在第j个数。
我们无法直接优化,那么我们分析一下什么条件下的k是最优的,假设另一个选择l,
因为k是最优的,所以:
dp[k]+(a[j]-a[k+1])^2-[dp[l]+(a[j]-a[l+1])^2]<0
dp[k]+a[k+1]^2-2*a[j]*a[k+1]-(dp[l]+a[l+1]^2-2*a[j]*a[l+1])<0
dp[k]+a[k+1]^2-(dp[l]+a[l+1]^2)<2*a[j]*(a[k+1]-a[l+1])
为了简化式子,令yk=dp[k]+a[k+1]^2,xk=2*a[k+1],同理有yl和xl
最终有(yk-yl)/(xk-xl)<a[j]
看看左式想到什么?没错,斜率。
那么令g[k,l]=(yk-yl)/(xk-xl)
若g[k,l]<a[j],说明k是比l更优的选择,我们可以排除l的冗余选择。
对应的,若有k<j<i,且g[i,j]<g[j,k],则j同样是冗余选择。
因为若g[i,j]<a[i],则说明i是比j更优的选择,
若g[j,k]>g[i,j]>a[i],则说明j不如k优,总之j是冗余选择。
那么右式的a[j]是已知的,而且a[j]是随j的增大而增大的,故g应满足:
即斜率单调增,整个图像呈下凸形式,所以我们就可以有单调队列的优化。
总结解题步骤:
1.删去队首已经不满足g[head,head+1]<a[i]的head
2.用q[head]更新dp[i][j]
3.删去队尾已经不满足g[i,tail]<g[tail-1,tail]的tail
4.将dp[i][j]加到队尾
参考程序:
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn=5100;
int f[maxn][maxn];
int a[maxn];
int n,m,head,tail;
int q[6*maxn];
int yy(int i,int j,int k){
return f[i-1][j]+a[j+1]*a[j+1]-(f[i-1][k]+a[k+1]*a[k+1]);
}
int xx(int j,int k){
return 2*(a[j+1]-a[k+1]);
}
int main(){
int T;
scanf("%d",&T);
int cnt=0;
while (T--){
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
sort(a+1,a+n+1);
for (int i=1;i<=n;i++){
f[1][i]=(a[i]-a[1])*(a[i]-a[1]);
}
for (int i=2;i<=m;i++){
head=tail=0;
q[tail++]=i-1;
for (int j=i;j<=n;j++){
while (head+1<tail && yy(i,q[head+1],q[head])<a[j]*xx(q[head+1],q[head]))
head++;
int k=q[head];
f[i][j]=f[i-1][k]+(a[j]-a[k+1])*(a[j]-a[k+1]);
while (head+1<tail && yy(i,j,q[tail-1])*xx(q[tail-1],q[tail-2])<=xx(j,q[tail-1])*yy(i,q[tail-1],q[tail-2]))
tail--;
q[tail++]=j;
}
}
printf("Case %d: %d\n",++cnt,f[m][n]);
}
return 0;
}
通过这一道题,我们可以发现一些寻找隐藏单调的方法:
1.根据题意,列出状态转移方程
2.先尽力优化
3.若仍无法通过,考虑满足什么条件的选择是较优的,将条件整理(将已知的写在一边,未知的写在另一边)并简化(换元)一下
4.分析简化后的条件,挖掘出单调性。
当然此题也可以