赞
踩
先来个模板题压压惊(bushi)。
单调队列分为“单调”和“队列”
以上面模板题中的最大值为例,分析一下单调队列的维护思路:
本题中队列维护的是某区间的最大值。
区间意味着有头有尾,尾即目前遍历到的下标i,头可以用长度来计算:max(1,i-len)。跟不上区间移动的,即下标在目前有效区间头之前的,踢出队列。
最大值的处理则比较有趣了:出现在a[i]之前比a[i]小的数一定不可能是区间max了(因为它与a[i]共处同一区间且a[i]更大),踢出去;但出现在a[i]之后比a[i]小的数有可能是区间max(a[i]是更先出队的),得先放在队列里待后续评估。
为实现元素的唯一标识与区间长度的记录,队列中维护的是数组中元素的序号(下标+1)。
不满足区间范围的,是从队头pop出去;
出现在a[i]之前比a[i]小的,从队尾pop出去;
根据前面的分析,无论如何a[i]都是要入队看看的。
细节:注意队列的初始化and队列的非空判定
贴模板题代码
- #include <iostream>
- #include <deque>
- using namespace std;
-
- #define maxn 1000005
- deque<int> sq, bq;
- int n, k;
- int a[maxn];
-
- int main() {
- cin >> n >> k;
- for (int i = 1; i <= n; i++) {
- cin >> a[i];
- }
- sq.push_back(0);
- for (int i = 1; i <= n; i++) {
- while(!sq.empty() && sq.front() <= i - k){
- sq.pop_front();
- }
- while (!sq.empty() && a[i] <= a[sq.back()]) {
- //cout << "pop" << sq.back() << endl;
- sq.pop_back();
- }
- sq.push_back(i);
- //cout << "push" << i << endl;
- if (i >= k) {
- cout << a[sq.front()] << " ";
- }
- }
- cout << endl;
-
- bq.push_back(0);
- for (int i = 1; i <= n; i++) {
- while (!bq.empty() && a[i] >= a[bq.back()]) {
- //cout << "pop" << bq.back() << endl;
- bq.pop_back();
- }
- bq.push_back(i);
- //cout << "push" << i << endl;
- if (i >= k) {
- if (bq.front() <= i - k) {
- //cout << "pop" << bq.front() << endl;
- bq.pop_front();
- }
- cout << a[bq.front()] << " ";
- }
- }
- cout << endl;
- return 0;
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
- #include <iostream>
- using namespace std;
-
- #define maxn 1000005
-
- int a[maxn];
- int q[maxn];
- int q2[maxn];
-
- int main() {
- int n, m;
- cin >> n >> m;
- for (int i = 1; i <= n; i++) {
- cin >> a[i];
- }
- int head = 0, tail = -1;
- q[0] = 0;
- for (int i = 1; i <= n; i++) {
- while (head <= tail && q[head] <= i - m)
- head++;//已经不在范围内要出队
- while (head <= tail && a[i] <= a[q[tail]])
- tail--;//维护单调递增的队列
- q[++tail] = i;
- if (i >= m)
- cout << a[q[head]] << " ";
- }
- cout << "\n";
-
- head = 0, tail = -1;
- q2[0] = 0;
- for (int i = 1; i <= n; i++) {
- while (head <= tail && q2[head] <= i - m)
- head++;//已经不在范围内要出队
- while (head <= tail && a[i] >= a[q2[tail]])
- tail--;//维护单调递增的队列
- q2[++tail] = i;
- if (i >= m)
- cout << a[q2[head]] << " ";
- }
- cout << "\n";
- return 0;
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
抽象一下这个题,其实是在求:不定长度且长度有大小限制(1<=len<=maxL)的最大子段和
插播一段关于“前缀和”:可以简单理解成数列前n项的和,不妨记为sum数组。那么子序列闭区间[i,j]的和则为 sum[j]-sum[i-1]
可参见题目:最大子段和 - 洛谷
插播结束。
即使子段和我们采用前缀和来计算,仍需一个二重循环里面遍历j的取值。
进一步地我们注意到,对于每个i来说sum[i]都是固定的,于是我们可以做如下图的数学推导
至此,我们已把问题转化为找到sum数组指定区间中的最小值,单调队列可以闪亮登场了。
本题是不允许子段的长度为0的,应注意出队的边界。
- #include <cstdio>
- #include <algorithm>
- #include <queue>
- using namespace std;
-
- #define maxn 500005
- int a[maxn];
- int sum[maxn];
- int ans = -1 << 30;
- deque<int>sq;
-
- int main() {
- int n, m;
- scanf("%d %d", &n, &m);
- for (int i = 1; i <= n; i++) {
- scanf("%d", &a[i]);
- }
- for (int i = 1 ; i <= n; i++) {
- sum[i] = sum[i - 1] + a[i];
- }
- sq.push_back(0);
- for (int i = 1; i <= n; i++) {
- while (!sq.empty() && sq.front() < i - m) {
- sq.pop_front();
- }
-
- ans = max(ans, sum[i] - sum[sq.front()]);
-
- while (!sq.empty() && sum[i] <= sum[sq.back()]) {
- //cout << "pop" << sq.back() << endl;
- sq.pop_back();
- }
- sq.push_back(i);
- }
- printf("%d\n", ans);
- return 0;
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
Watching Fireworks is Fun - 洛谷
设表示在放第i个烟花时,所处位置为j所能获得的最大值。
根据题意可以列出第一行的式子,但注意到是常数可以提出去,于是可以得到第二行的式子。
对于固定的i和j来说,和j都是常量,可以提出去,于是可以得到第三行的表达式。
根据第三行的式子不难看出,我们需要维护一个关于的优先队列,来辅助更快地计算出
由于计算第i维时,只需要用到第i-1维的数据,所以我们的dp只需要两行就够了,轮流当第i-1维和第i维。
又由于行数分别为0和1,可以通过异或的操作来互换。
- #include <iostream>
- #include <algorithm>
- #include <cmath>
- #include <deque>
- using namespace std;
-
- #define maxn 150005
- #define maxm 305
- typedef long long ll;
- ll a[maxn], b[maxn], t[maxn];
- //ll dp[maxm][maxn];
- ll dp[2][maxn];int fl=1;
- int q[maxn];
- ll ans = -1 << 30;
-
-
- int main() {
- int n, m, d;
- cin >> n >> m >> d;
- for (int i = 1; i <= m; i++) {
- cin >> a[i] >> b[i] >> t[i];
- }
-
- for (int i = 1; i <= m; i++) {
- int l=1,r=0,k=1;
- for (int j = 1; j <= n; j++) {
- for (; k <= min(n*1ll, j + (t[i] - t[i - 1])*d); k++) {
- while(l<=r && dp[fl^1][q[r]]<=dp[fl^1][k]) r--;
- q[++r]=k;
- }
- while(l<=r&&q[l]<max(1ll,j-(t[i]-t[i-1])*d)) l++;
- dp[fl][j]=dp[fl^1][q[l]]-abs(a[i]-j)+b[i];
- //dp[i][j] = max(dp[i - 1][k] - abs(a[i] - j) + b[i], dp[i][j]);
- }
- fl^=1;
- }
- for(int j = 1;j<=n;j++) ans=max(ans,dp[fl^1][j]);
- cout << ans << "\n";
- return 0;
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
参考资料:
OI-wiki
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。