单调队列:从一道题说起_2023CCPC广东省赛B.基站建设

news/2024/10/10 3:23:50

今天遇到一道题:

给定长度为n的数组a,a[i]表示在第i点建立基站的开销。
同时给出m个区间[li,ri],要满足给定的m个区间内都至少有一个基站,求最小的开销。

正解是单调队列优化dp,那么什么是单调队列?我们先看另外一道题:

image

显然最小值和最大值是互相独立的,我们可以先考虑最大值。用手模拟一下这个过程:

  1. [1 3 -1 -3 5 3 6 7]
    最开始我们的窗口里什么也没有,我们把1加入备选答案。
    现在的答案备选:[1]

  2. [1 3 -1 -3 5 3 6 7]
    新进来一个3,发现3比1大,并且3比1还更有未来!!更有未来指的是在窗口往右滑动的过程中,1会先淘汰掉,3在1后面,3的"有效期"更久。那3不仅比1大,有效期还更久,1两个都比不过,永远不会对最大值有贡献了,我们的备选答案里就可以淘汰掉1,加入3
    现在的答案备选:[3]

  3. [1 3 -1 -3 5 3 6 7]
    新进来一个-1,-1并没有比3大,但是我们现在还不知道后面的数会有多小,来个-100,-inf什么的,在往右滑动的过程中3会被踢掉,此时-1就可能成为最大值。所以暂且把-1加入备选答案。
    现在的答案备选:[3,-1]

  4. [1 3 -1 -3 5 3 6 7]
    新进来一个-3,同理,-3比-1小,但是有效期是最新的,我们现在还不知道后面的数会有多小,来个-100,-inf什么的,在往右滑动的过程中3和-1会被踢掉,此时-3就可能成为最大值。所以暂且把-3加入备选答案。
    现在的答案备选:[3,-1,-3]

  5. [1 3 -1 -3 5 3 6 7]
    新进来一个5,发现此时的5不仅比-3大,还比-1、3都大,且有效期最新,所以把3/-1/-3都淘汰掉,只留一个5.
    现在的答案备选:[5]

  6. [1 3 -1 -3 5 3 6 7]
    新进来一个3,虽然3比5小,但有效期新,加进来。
    答案备选[5,3]

  7. [1 3 -1 -3 5 3 6 7]
    新进来一个6,比现有的答案都大,且有限期更新,淘汰掉现在的答案备选。
    答案备选[6]

......

在这个过程中大家也发现,如果新加进来一个数,当前答案备选里的“队尾”哪哪都比不过人家时(不仅有效期短,数值还更小),就永远不会对答案有贡献,我们就可以踢掉队尾。但也不一定只踢掉一次,为什么呢?

假设现在答案备选里有[1000,666,233,-1,-7],新加进来一个7。

比较当前的队尾-7:数值更小,“有效期”更短,用7来代替-7肯定会是更优的解,把-7踢掉。答案备选变成[1000,666,233,-1]

比较当前的队尾-1:同理,用7代替-1,答案备选变成[1000,666,233]

比较当前的队尾233:虽然233可能很快就失效了(窗口要滑过去,233要没了),但是它的值更大,在失效前可能会成为最优解,先保留着。这时在队尾加入新进来的7,答案备选就变成了[1000,666,233,7]。

也就是说,我们在维护一个单调递减的答案备选队列,每新加进来一个数 x,就考虑能否用 x 替代掉队尾,如果发现可以,则一直这么做,直到当前的队尾比 x 大。

这部分的代码是:

int h = 1, t = 0,q[1000005],p[1000005]; //q存储值,p存储位置
for (int i = 1; i <= n; i++) {while (h <= t && q[t] <= a[i]) t--;  //如果队尾不优,就一直淘汰掉t++; //加入新的数q[t] = a[i];p[t] = i;
}

你可能会发现我们上面一直没有考虑过k的限制,k怎么处理?不就是要让当前的队头不能过期,否则就要踢出去嘛。那就是:

while (p[h] <= i - k) h++; //如果p[h]<= i-k 说明当前的h离i超过了k,已经离开了窗口 ,就h++。

这样我们就用单调队列解决了这道滑动窗口。再回来看

给定长度为n的数组a,a[i]表示在第i点建立基站的开销。
同时给出m个区间[li,ri],要满足给定的m个区间内都至少有一个基站,求最小的开销。

我们能想到最暴力的dp表达式:令 dp[i] 表示强制在 i 建立基站,且前面所有线段都满足要求的最小花费,则

dp[i]=max(dp[j]+a[i]) ,其中[j+1,i-1]之间不能有完整的区间(不然就有区间没被覆盖到了)

看起来这个方程是O(N^2)的,因为我们要一层for循环枚举i,再一层for循环枚举j。但事实上由于"[j+1,i-1]之间不能有完整的区间"这一限制,想象一下,对于一个i,它的j只能是往前一小段。

image

比如上图,合法的区间只有绿色那段,如果再从更前面的点转移过来,就会导致最上面的那段区间没被覆盖到。

因此我们可以这么处理:

cin>>m;while(m--){int l,r; cin>>l>>r;pre[r+1]=max(pre[r+1],l);}

image

读入一段区间[l,r]后,对于r+1来说最小的合法前驱必须至少是l,再往前就会导致区间[l,r]不被覆盖。因为可能同一个r对应很多的l,所以取个最大值。

但只考虑端点还是不够的,可能出现这种情况:

image

本来对于r'+1来说最小的合法前驱至少得是l',对于R+1来最小的合法前驱至少得是L,但这显然是错的。如果要在R+1建立基站,最小的合法前驱得是l',否则[l',r']这段无法覆盖到。也就是说当前点的合法前驱还跟前面的点有关,取一个前缀max即可。

for(int i=1;i<=n;i++) pre[i]=max(pre[i-1],pre[i]);

然后正式开始dp,和刚才的滑动窗口类似:
处理队头的操作:

while(hh<=tt and q[hh]<pre[i]) hh++; //如果当前队头超出了合法的范围,也就是说“过期了”,把队头踢掉

处理队尾的操作:

while(hh<=tt and dp[q[tt]]>=dp[i]) tt--; //如果当前的答案比队尾还优(这里更小是更优),且因为是新加进来的,有效期更新,更不容易超出合法的范围。
//当前的答案怎么看都更好,就把队尾踢掉。
//最后队列里就是合法的、且答案单调递增的备选答案

dp转移操作:

dp[i] = dp[q[hh]] + a[i]; //因为我们的队列里维护的是合法的、答案单调递增的备选答案,所以直接取队首就是最优解

最后合起来完整代码就是:

#include<bits/stdc++.h> 
using namespace std;
typedef long long LL;
int n,m;
void solve(){cin>>n;vector<int> a(n+5),pre(n+5),q(n+5);vector<LL> dp(n+5);for(int i=1;i<=n;i++) cin>>a[i];n++;cin>>m;while(m--){int l,r; cin>>l>>r;pre[r+1]=max(pre[r+1],l);}for(int i=1;i<=n;i++) pre[i]=max(pre[i-1],pre[i]);int hh=0,tt=0;q[0]=0;for(int i=1;i<=n;i++){while(hh<=tt and q[hh]<pre[i]) hh++;dp[i] = dp[q[hh]] + a[i];while(hh<=tt and dp[q[tt]]>=dp[i]) tt--; q[++tt]=i;}cout << dp[n] << endl;return ;
}
int main(){int t;cin>>t;while(t--){solve();}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.ryyt.cn/news/27621.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

Raven-1-WordPress-python命令提权

靶机下载地址:https://www.vulnhub.com/entry/raven-1,256/ 修改hosts文件DescriptionRaven is a Beginner/Intermediate boot2root machine. There are four flags to find and two intended ways of getting root. Built with VMware and tested on Virtual Box. Set up to …

Raven-2-WordPress-UDF提权

0x00 什么是UDF UDF 全称为:User Defined Function,意为用户自定义函数;用户可以添加自定义的新函数到Mysql中,以达到功能的扩充,调用方式与一般系统自带的函数相同,例如 contact(),user(),version()等函数。 udf 文件后缀一般为 dll,由C、C++编写。 0x01 UDF在渗透中…

探讨宝塔切换php版本切换失败的原因和解决方法

宝塔切换php版本是非常简单的操作,但是有时候切换失败可能会导致系统无法正常工作,给我们带来很大的麻烦和困扰。在本文中,我们将探讨可能导致宝塔切换php版本失败的一些常见原因和解决方法。一、检查宝塔是否已成功安装PHP版本在切换PHP版本之前,请确保您已经正确地安装了…

二叉树相关的三个常见算法题

算法题一// 计算一颗二叉树的所有节点的数量 int BinaryTree_CountNode(Tnode_t *root) {int n1, n2;if (NULL == root){return 0;}n1 = BinaryTree_CountNode(root->lchild);n2 = BinaryTree_CountNode(root->rchild);return n1 + n2 + 1; }算法题二// 计算一颗二叉树的…

金汇龙王战神程序智慧管家app拨号精灵下载说明

金汇战神程序App下载,龙王程序app,智慧管家下载安装 厂家售后使用说明及安装教程:金汇战神系金汇科技出品战神程序,无区域限制,高性价比高,调试安装更加快捷方便,安装时间大大缩短。添加微心 ZSMJCC 咨询索取金汇相关App下载链接 手机上安装好金汇战神小精灵app后,连接上…

电脑是组装的好还是原装的好?

组装电脑和原装电脑孰优孰劣一直备受争议。困扰着无数人的问题:组装电脑和原装电脑到底哪个更出色?下面php小编新一将为您详细介绍这两者的优缺点,帮助您做出明智的选择。继续阅读,探索组装电脑的灵活性、升级潜力和性价比,以及原装电脑的稳定性、保修和简便性。电脑是组装…