上篇博客说好的这篇博客是单调队列的直接进阶,但是,咕咕咕。个人感觉,单调队列比单调栈难调多了啊,单点队列涉及了两个指针来控制数列,因为要维护数列的单调性,还要维护数列先进先出的原则,所以需要一个头指针来确定每次要出队列的元素,然后一个尾指针来控制队列元素的加入,同时还要保持队列的单调性,比单调队列确实难了那么一点,导致一个 模板题调了差不多半天吧,最后还是修改了自己的写法才A到,最近又变菜了呢。
个人觉得在维护单调队列的过程中,关键是头指针的移动跟尾指针的关系,在删除队首元素,以及队尾收缩的时候需要判断队列是否为空,一般情况是head == tail的情况出现时,即代表队列此时是空值。
单调队列维护的过程类似于单调栈。
取队首元素:判断队列是否为空,若不为空,取队首元素,head++
元素入队 :在维护数列单调性的前提下,逐渐删除队尾元素,删除队尾元素的同时,需要注意队列是否为空,知道队列中的元素满足单调性,此时,元素入队。
以一个单调队列的入门题洛谷1090 合并果子 为例
首先,此题的求解方法有很多,优先选择优先队列啊,写起来方便,但是要拿单调队列练练手嘛。好多人说用单调队列的复杂度是O(n),好吧,原队列不排序就直接做吗?我很菜并不知道这时间复杂度怎么 算的,快排不就O(nlogn)了。需要维护两个队列,一个是原对列,一个合并后的数字组成的序列。每次从两个队列的队首取值,然后就是判断大小的问题了。可以发现,每次取值合并后的数值永远大于合并队列的数值,所以这数列自然单调,不用太多维护,只需要注意取队首元素时候队列是否为空,入队列时候取值问题即可。
代码实现:
/*
Look at the star
Look at the shine for U
*/
#include
#define sl(x) scanf("%lld",&x)
using namespace std;
typedef long long ll;
const int N = 1e6+5;
const ll mod = 1e9+7;
const int INF = 0x3f3f3f3f;
ll s[N],que[N],tail,head;
int main()
{
ll n,i,j,k;
sl(n);
for(i = 1;i <= n;i++) sl(s[i]);
if(n <= 1) {puts("0");return 0;}
sort(s+1,s+1+n);s[n+1] = INF;
ll ans = s[1]+s[2],s_haed = 3;
que[tail] = s[1]+s[2];que[2] = INF;
for(i = 3;i <= n;i++)
{
que[++tail] = 0;
for(j = 1;j <= 2;j++)
{
if(s[s_haed] > que[head] && head != tail) que[tail] += que[head],head++;
else que[tail] += s[s_haed],s_haed++;
}
ans += que[tail];
que[tail+1] = INF;
}
printf("%lld\n",ans);
}
依旧是HDU1506
若是此题用单调队列维护的话,需要考虑维护的队列是递增还是递减,因为要找两侧的第一个比他小的值,所以出队列的元素应该跟比他小的值有关系,所以可以设为递增队列。然后就是研究队列问题,本来想在单调找的基础上直接改,但是后来发现队列的操作似乎难于栈,当维护栈的时候,最后的栈顶元素就是Left_first_min,但是队列却不是这样子的,模拟一下。
1.2入队列,作为左边的起始位置,此时队列中元素2
2.1入队列,保持递增,删除队尾,此时队列中元素1
3.4入队列,直接添加,此时队列元素1,4
4.5入队列,直接添加,此时的队列元素1,4,5
5.1入队列,需要删数,此时队列元素1,1
6.3入队列,直接添加,此时队列元素1,1,3
7.3入队列,直接添加,此时队列元素1,1,3,3
此时发现,出队列的时候的值的Right_first_min就是当前的新入队列的元素,然后tail-1的值其实就是Left_first_min,然后这不就是单调栈嘛,所以嘛,还是用单调 队列写一下,换种写法实现一下,了解一下单调队列的写法。这样,从两边分别维护第一次出现的最小值,最后得到答案,所以,能用单调栈的地方,最好不用单调队列,这个题不是典型的单调队列的题目,我也只是用单调队列的方式实现了一下。
代码实现:
/*
Look at the star
Look at the shine for U
*/
#include
#define sl(x) scanf("%lld",&x)
using namespace std;
typedef long long ll;
const int N = 1e6+5;
const ll mod = 1e9+7;
const int INF = 0x3f3f3f3f;
ll s[N],que[N],head,tail,L[N],R[N];
int main()
{
ll n,i,j,k,t;
while(~scanf("%lld",&n) && n)
{
head = tail = 0;
for(i = 1;i <= n;i++) sl(s[i]),R[i] = n+1,L[i] = 0;
que[0] = 1;
for(i = 2;i <= n;i++)
{
while(head <= tail && s[que[tail]] > s[i]) R[que[tail]] = i,tail--;
que[++tail] = i;
}
head = 0,tail = 0;
que[0] = n;
for(i = n-1;i;i--)
{
while(head <= tail && s[que[tail]] > s[i]) L[que[tail]] = i,tail--;
que[++tail] = i;
}
ll maxx = 0;
for(i = 1;i <= n;i++) maxx = max(maxx,1ll*(R[i]-L[i]-1)*s[i]);
printf("%lld\n",maxx);
}
}
单调队列多出现在DP优化中,典型的就是移动窗口问题,这里只对单调队列做一个简单介绍,实际应用还是要写一篇单调队列进阶的文章,继续挖坑。