2025dsfz集训Day11: 单调队列优化DP

news/2025/1/23 19:38:17/文章来源:https://www.cnblogs.com/FrankWKD/p/18688549

单调队列优化DP

单调队列

  • 队列是单调的,递增或递减
  • 只能在队首或者队尾进行操作
  • 队列中维护所有在窗口中的元素,有一些元素是没用的,以区间最大值为例:
    • 所以从左到右尝试加入队列,弹出队尾比当前数更小的元素,弹出队首已经出窗口的元素,再队尾压入当前数
    • 这样,队首就是窗口最大值
  • 每个数只会弹入弹出一次,复杂度O(n)
点击查看单调队列模版
void getmax() {  // 和上图同理int head = 0, tail = -1;for (int i = 1; i <= k; i++) {while (head <= tail) {tail--;  // 移动窗口(见下文)+优化队列}q[++tail] = i;  // 入队}for (int i = k; i <= n; i++) {while (head <= tail && a[q[tail]] <= a[i]) tail--;  // 排除没用的值,优化队列内容。(详见上图)cout << a[q[head]] << "";                           // 输出最大值}
}
经典例题:[洛谷P1886 滑动窗口 /【模板】单调队列](https://www.luogu.com.cn/problem/P1886) ### 题意 - 给出一个长度为 $n$ 的数组,编程输出每 $k$ 个连续的数中的最大值和最小值。 - $n,k≤1e6$
点击查看代码
#include <bits/stdc++.h>
using namespace std;int a[1001010];
deque <int> id;
deque <int> q;
int k, n;
int main() {cin >> n >> k;for (int i = 1; i <= n; i++) cin >> a[i];for (int i = 1; i <= k; i++) {while (!q.empty() and q.back() > a[i]) {q.pop_back();id.pop_back();}q.push_back (a[i]);id.push_back (i);}for (int i = k + 1; i <= n + 1; i++) {cout << q.front() << " ";
//		cout << q.back() << endl;if (id.front() < i - k + 1) {id.pop_front();q.pop_front();}while (!q.empty() and q.back() > a[i]) {q.pop_back();id.pop_back();}q.push_back (a[i]);id.push_back (i);}for (int i = 1; i <= k; i++) {while (!q.empty() and q.back() < a[i]) {q.pop_back();id.pop_back();}q.push_back (a[i]);id.push_back (i);}cout<<endl;for (int i = k + 1; i <= n + 1; i++) {cout << q.front() << " ";
//		cout << q.back() << endl;if (id.front() < i - k + 1) {id.pop_front();q.pop_front();}while (!q.empty() and q.back() < a[i]) {q.pop_back();id.pop_back();}q.push_back (a[i]);id.push_back (i);}
}

最大连续和

  • 给你一个长度为n的整数序列。要求从中找一段连续长度不超过m的子序列,并且和最大
  • 求前缀和,转化成:

\[a[i]=max(sum(i)-sum(i-k)|k=1\dots m) \]

  • 即:

\[a[i]=sum(i)-min(sum(i-k)|k=1\dots m) \]

  • 滑动窗口求第二项即可
点击查看代码
int MaxSubSequence(const int A[], int N){  int ThisSum,MaxSum,i,j,k;  MaxSum = 0;  for(i=0;i<N;i++)  {  for(j=i;j<N;j++)  {  ThisSum = 0;  for(k=i;k<=j;k++)  {  ThisSum += A[k];  }  if(ThisSum > MaxSum)  MaxSum = ThisSum;  }  }  return MaxSum;  
}   

修建草坪

  • \(FJ\)\(N\) (\(1 <= N <= 100,000\))只排成一排的奶牛。每只奶牛的效率是不同的,奶牛 \(i\) 的效率为 \(E_i\)
    计算 \(FJ\) 选奶牛可以得到的最大效率,并且该方案中没有连续的超过 \(K\) 只奶牛。

  • \(dp[i][0]\) 表示以i为结尾不选i的最大值, \(dp[i][1]\) 表示以 \(i\) 为结尾选 \(i\) 的最大值:

\[dp[i][0]=max(dp[i-1][0],dp[i-1][1]) \]

\[dp[i][1]=max(dp[j][0]+sum[i]-sum[j]|i-k\le j\le i-1) \]

转化成

\[dp[i][1]=sum[i]+max(dp[j][0]-sum[j]|i-k\le j\e i-1) \]

可以单调队列优化.

旅行问题

  • \(John\) 打算驾驶一辆汽车周游一个环形公路。公路上总共有 \(n\) 个车站,每站都有若干升汽油(有的站可能油量为零),每升油可以让汽车行驶一千米。 \(John\) 必须从某个车站出发,一直按顺时针(或逆时针)方向走遍所有的车站,并回到起点。在一开始的时候,汽车内油量为零,\(John\) 每到一个车站就把该站所有的油都带上(起点站亦是如此),行驶过程中不能出现没有油的情况。
  • 判断以每个车站为起点能否按条件成功周游一周。

解法

  • 拆环成链,设每个加油站有 \(d[i]\) 油,到下一个加油站要 \(s[i]\) 千米
  • 那么从一个点出发,\(d[i]-s[i]\) 前缀和必须是非负数
  • 在 $2n的链上维护 \(n\) 的滑动窗口,求区间最小值,判断是不是负数

BANK NOTEs

  • 一共有 \(n\) 种面值的硬币,面值分别为 \(b_1, b_2,..., b_n\). 但是每种硬币有 \(c_i\) 个,现在我们想要凑出面值 \(k\) 求最少要用多少个硬币.

  • \[n≤200,b_i,c_i,k≤20000 \]

单调队列优化多重背包

不了解背包 \(DP\) 的请先阅读背包 \(DP\)。设 \(f_{i,j}\) 表示前 \(i\) 个物品装入承重为 \(j\) 的背包的最大价值,朴素的转移方程为

\[f_{i,j}=\max_{k=0}^{k_i}(f_{i-1,j-k\times w_i}+v_i\times k) \]

时间复杂度 \(O(W\sum k_i)_0\)
考虑优化\(f_i\)的转移。为方便表述,设\(g_{x,y}=f_{i,x\times w_i+y}, g'_{x,y}=f_{i-1,x\times w_i+y}\),其中\(0\leq y<w_i\),则转移方程可以表示为:

\[g_{x,y}=\max_{k=0}^{k_i}(g'_{x-k,y}+v_i\times k) \]

\(G_{x,y}=g'_{x,y}-v_i\times x\)。则方程可以表示为:

\[g_{x,y}=\max_{k=0}^{k_i}(G_{x-k,y})+v_i\times x \]

这样就转化为一个经典的单调队列优化形式了。\(G_{x,y}\) 可以 \(O(1)\) 计算,因此对于固定的 \(y\),我们可以在 \(O\left(\left[\frac{W}{w_i}\right]\right)\) 的时间内计算出 \(g_{x,y}\)。因此求出所有 \(g_{x,y}\) 的复杂度为

\[O\left(\left[\frac{W}{w_i}\right]\right)\times O(w_i)=O(W)。 \]

这样转移的总复杂度就降为 \(O(nW)\)

核心代码

点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N=205,M=20005;
int n,m,B[N],C[N],dp[M];
struct Data {int Shuz,Weiz;
} Ddq[M];
int main() {int i,j,k;cin>>n;for(i=1; i<=n; i++) cin>>B[i];for(i=1; i<=n; i++) cin>>C[i];cin>>m;memset(dp,63,sizeof dp);dp[0]=0;for(i=1; i<=n; i++) {for(j=0; j<B[i]; j++) {int Head=1,Tail=0;for(k=0;; k++) {int x=k*B[i]+j;if(x>m) break;while(Head<Tail&&Ddq[Head].Weiz<k-C[i]) Head++;while(Head<=Tail&&dp[x]-k<Ddq[Head].Shuz-Ddq[Head].Weiz) Tail--;Ddq[++Tail]= {dp[x]-k,k};dp[x]=min(dp[x],Ddq[Head].Shuz+k);}}}cout<<dp[m]<<endl;return 0;
}

烽火传递

  • 在某两座城市之间有 \(n\) 个烽火台,每个烽火台发出信号都有一定的代价。为了使情报准确的传递,在连续 \(m\) 个烽火台中至少要有一个发出信号。现输入 \(n\)\(m\) 和每个烽火台发出的信号的代价,请计算总共最少需要话费多少代价,才能使敌军来袭之时,情报能在这两座城市之间准确的传递.
  • \[1<=m<=n<=1,000,000 \]

  • 状态表示:\(f[i]\) 标识前 \(i\) 个烽火台并点燃第 \(i\) 个烽火台的最小合法代价.
  • 状态转移:\(f[i]=min(f[j]+w[i],i-m\le j\le i-1)\),最后扫描 \(m\)\(f[i]\) 的值。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
int dp[100010];
int a[100010];
int q[100010];
int main(){int n,m;scanf("%d%d",&n,&m);int head=1;//表示队首的下标。int tail=1;//表示队尾的下标。for(int i=1;i<=n;i++){scanf("%d",&a[i]);}for(int i=1;i<=n;i++){dp[i]=dp[q[head]]+a[i];while(tail>=head&&dp[i]<=dp[q[tail]])//每次更新都表示弹出队尾元素。tail--;q[++tail]=i;//弹完后,我们保证这个队列是单调的且新加入的元素一定是队尾元素。while(q[head]<i+1-m)//判断队首元素是否合法,如果不合法,将其弹掉。head++;}printf("%d",dp[q[head]]);//这一步比较经典。我们维护的单调队列中,队首元素一定是合法的(在最后的m个烽火台之内),//所以我们选择这其中的最小者更新即可return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

必应搜索中,当地区设置为美国时出现的异常(未解决)

描述 在设置中,设置地区。加载时,会警告如下: An iframe which has both allow-scripts and allow-same-origin for its sandbox attribute can escape its sandboxing.这个大致的意思是,allow-scripts允许运行脚本,allow-same-origin视为和宿主网页同源。 具体的影响不清…

做抖音矩阵是否需要很多台手机?

做抖音矩阵是否需要很多台手机?做抖音矩阵不一定要很多台手机,可依据不同情况选择合适方式: 使用多台手机 优势:物理隔离确保账号间完全独立,极大降低因设备关联导致的风险。比如,若一台手机上的账号因违规操作被封,不会影响其他手机上的账号。同时,多台手机能同时进行…

【Neo4j知识图谱数据库】入门

在当今大数据和人工智能的时代,知识图谱作为一种高效的数据表示和查询方式,逐渐受到广泛关注。本文将带大家从入门到精通,了解知识图谱及其存储工具Neo4j的方方面面,包括知识图谱的介绍、Neo4j的特点、安装步骤、具体的使用方法(创建、查询),以及Cypher查询语言的详细讲…

jumpserve Linux 单机部署

参考文档:https://docs.jumpserver.org/zh/v3/installation/setup_linux_standalone/offline_upgrade/1、环境要求 1.1、操作系统1.2、数据库2、离线安装从飞致云社区 下载最新的 linux/amd64 离线包, 并上传到部署服务器的 /opt 目录https://community.fit2cloud.com/#/produ…

【AI应用开发】 向量和向量数据

一、什么是向量 向量vector 通常出现在自然语言NLP领域,NLP中称为词嵌入word embedding,词嵌入的工作就是如何将人类语言中的词汇、短语或句子转化为计算机能够理解和操作的数学向量。具体的,词嵌入(Word Embedding),是一种将词汇表中的每个单词或短语映射到一个固定大小…

云--什么是rest

https://www.whatisrest.com/已停用已被其他组织或个人用于其他服务

FDUWC2025 游记

夏虫振翅向灼热光明cnblogs 链接 退役老人人生第一次在网上写游记 qwq Day 0 到达国际大都市上海。这是我第二次来上海玩了,但是感觉 fdu 那一片和我印象中的上海不一样。上一次来没有注意,这一次来发现上海的车道少且窄,容易堵车,而且绿牌车的占比看起来比广州都高。冬天的…

Markdown学习day01

Markdown语法学习 标题:用#加Enter键:# +标题,有几个#就是几级标题 #+空格+标题 ##+空格+标题 ###+空格+标题 ####+空格+标题 #####+空格+标题 字体 粗体,两边加**; 字体 斜体,两边加*; 字体 斜体加粗,两边加***; 字体 删除线,两边加~~; 字体 引用 使用>+空格引用…

WordPress Technical Stack

- [Wordpress - WordPress Tech Stack](https://stackshare.io/wordpress/wordpress)StackStack DecisionsApplication and Data (3)PHPNGINXEdgeCastUtilities (4)Google AnalyticsElasticsearchKISSmetricsUserTestingDevOps (1)GruntBusiness Tools (2)WordPressHelpshift

【互联网行业】2024中国网络安全产业势能榜优能企业「互联网行业」典型案例展示

互联网行业的快速发展使其成为了信息安全的重灾区。网站、应用、平台以及用户数据面临着越来越复杂的安全威胁。如何通过技术手段增强网络安全防护,保障用户信息的隐私安全,成为了互联网企业的重要任务。我们将展示互联网行业中一些典型的案例,分析其在网络安全方面的创新措…

【Milvus向量数据库】AI应用开发

一、Milvus介绍 上一小节中,全面介绍了向量和向量数据库,今天详细介绍下其中比较出名的开源数据库Milvus。希望对你有帮助Milvus 是一个开源的、高性能的向量数据库,专为海量向量数据的快速检索而设计。在人工智能、计算机视觉、推荐系统和其他需要处理大规模向量数据的领域…

PassGet:一款使用Go语言编写的用于后渗透测试阶段提取Windows平台下常见的应用程序

免责声明 仅限用于技术研究和获得正式授权的攻防项目,请使用者遵守《中华人民共和国网络安全法》,切勿用于任何非法活动,若将工具做其他用途,由使用者承担全部法律及连带责任,作者及发布者不承担任何法律连带责任项目介绍 PassGet是一款使用Go语言编写的用于后渗透测试阶段…