Day 3 A - Ascending Rating HDU 6319 | 单调队列 | 思维

本文介绍如何使用单调队列解决最大子序和问题,重点讲解队列中head和tail指针的运用,以及如何正确计算最大值和计数(cnt)。探讨了正向与逆向使用单调队列的区别,并提供了实现代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

补题的时候去看了单调队列,贴一个最简单的最大子序和,有助于以后记不清楚的时候回顾一下。主要是注意head,tail的使用,具体表示什么,怎么判断±的条件。

这道题需要注意的是,教练每一次挪动区间以后,都会重置Maxrating = -1, cnt = 0,所以才会需要我们使用单调队列。因为就算你对每个区间统计,可是如果每移动一次不重置,仔细想想就跟整个数列的Max更换没有区别。

He starts to compare the rating of the contestants. He will pick a continous interval with length m, say [l,l+m−1], and then inspect each contestant from left to right. Initially, he will write down two numbers maxrating=−1 and count=0.
注意这段话……读的时候一直没读清楚啊orz

再一点是,区间长度规定为m,假如[a, b]区间的长度为m,那就是b - a + 1 = m。
这两点是我做的时候一直理解出错的地方……后来补题也是想不懂cnt怎么就18次了Orz,怎么区间就取了6个了。

关于单调队列我最开始理解的比较困难,现在是这么想的,单调队列一般是解决一个局部的连续的问题(相对于整体,不仅仅是区间连续,整个过程也是连续的),我们是通过控制循环里的i来控制区间的数目,Head tail来控制队列内容的改变,用数组来盛放队列。每一个i对应当前状态的一个队列。i更换,窗口向前滑动,就通过head,tail来改变队列的状态。

关于head tail的选取根据我们要递增还是递减,比如本题我们是逆着求严格递减,数据如下
3 2 2 1 5 7 6 8 2 9
9 2 8 6 7 5 1 2 2 3
第一个区间9 2 8 6 7 5,head指向9,tail指向5,因为是反着的对应回去原来的就相当于掉了个头。在判断前后关系的时候应该是head >= tail合法。

其实如果要我们求区间最大值并不难,我们维护一个严格递增序列,那么每个区间的Head就是最大值。但是这时候我们求cnt会非常麻烦。
这道题我觉得困难的地方就在这里:

  • 逆着用单调队列
  • 此时这个严格递减序列元素的数目就是当前的cnt

等等,你可能会问了,为什么正着求cnt就会麻烦?我们写一下正着求的时候这

3 2 2 1 5 7 6 8 2 9

1 5 7
1 5 6
1 5 6 8
1 5 6 8
5 6 8 9

也是每个序列元素的数目就是当前的cnt对不对!但是,如果我们这么求,我们就没法确定最大值了。因为维护严格递增的时候,我们要确保后面一个元素尽可能小,假如它小于队尾,那就pop_back,继续向前比较。为什么要这么做,是因为我们是求局部连续区间的某种递增递减子序列!注意!是子序列!subsequence!

然后就可以开始写代码了,还有两个小问题要注意

  • 队列元素个数是tail - head,不是tail(因为我们在移动他们!)
  • p,q必须使用ll
#include <bits/stdc++.h>

using namespace std;

#define ll long long
#define endl '\n'

const int MAX_N = 1e7;

//ll n, m, k, p, q, r, MOD, T;
ll maxrating, cnt;
ll num[MAX_N + 10];
ll p, q;
int n, m, k, MOD, T, r;
int que[MAX_N + 10], head, tail;

int main()
{
	scanf("%d", &T);

	while (T--)
	{
	    //printf("intoTloop\n");

		scanf("%d%d%d%lld%lld%d%d", &n, &m, &k, &p, &q, &r, &MOD);
		for(int i = 1;i <= k;i++)
        {
            scanf("%lld", &num[i]);
        }
		for(int i = k+1;i <= n;i++)
        {
            num[i] = (p * num[i-1] + q * i + r)%MOD;
        }

        //printf("into i loop\n");

        head = tail = 0;
        cnt = 0, maxrating = 0;
        for(int i = n;i >= 1;i--)
        {
            //printf("%d\n", i);
            while(head < tail && que[head] - i > m - 1)
                head++;

            while(tail > head && num[i] >= num[que[tail-1]])
                tail--;

            que[tail++] = i;

            if(i <= n-m+1)
            {
                maxrating += num[que[head]] ^ i;
                //cnt += tail;
                cnt += (tail - head) ^ i;
            }

        }

        printf("%lld %lld\n", maxrating, cnt);

	}

	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值