单调队列dp例题

博客围绕三道算法题展开,包括2022江苏编程竞赛的跳跃寻宝题、CF的烟花观赏题和NOI2005的瑰丽华尔兹题。详细介绍了题目大意,并针对每道题给出了基于动态规划和单调队列的解题思路,还提及了时间复杂度等信息。

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

1.2022 Jiangsu Collegiate Programming ContestC. Jump and Treasure

Tom likes playing a video game recently. The rules of this game are as follows. The game is played on an x-axis. There are a total of n+1 pillars in the game, which are arranged in a row from left to right. The pillars are numbered from 0 to n. The coordinate of the pillar numbered iii is x=i. There is also an infinite platform in the game in the range of [n+1,+∞). Players can win by jumping to any position within this range.

Players start from the pillar numbered 0 and can only jump from left to right, i.e. the coordinates must increase. And he can only jump on the pillars or the platform, otherwise, he will fall into the void and fail the game. In addition, his jumping ability is limited, and the distance of each jump does not exceed p.

Except for the pillar numbered 0, there will be a treasure chest on each remaining pillar. The treasure chest on the pillar numbered i will have ai gold coins. However, there are some trap chests (ai<0) where he will lose |ai| gold coins.

This game has n levels. Tom can only jump to the pillars numbered with multiples of i in the i-th level. Now there are q queries, each of which contains a number x, asking the maximum number of gold coins Tom can get when he wins at level x. It’s possible that Tom opens too many trap chests on the way and gets negative gold coins.

Input
The first line contains three integers n, q, and p (2≤p≤n≤106,1≤q≤1062≤p≤n≤10^6, 1≤q≤10^62pn106,1q106), indicating the number of pillars, the number of queries, and the longest jump distance.

The second line contains n integers a1,a2,…,ana_1,a_2,…,a_na1,a2,,an (|ai|≤10910^9109), indicating the number of gold coins in the treasure chest on the pillar numbered i.

In the next q lines, each line contains an integer x (1≤x≤n), indicating a query of the maximum number of gold coins he can obtain in level x.

Output
Output one integer in a single line for each query, representing the answer. If it is impossible to win, output Noob in a single line.

Exam
input

5 3 4
2 5 -6 -4 3
1
2
3

output

10
5
-6

input

10 6 8
5 4 -6 8 -11 5 -6 4 -7 3
1
2
4
6
8
10

output

29
24
12
5
4
Noob

题目大意

有n+1个柱子(从0到n),每个距离为1,每个柱子上有宝箱,宝箱的金币可正可负,跳到n+1及外面即可过关,求最大的金币数。q次询问,并且跳跃间隔不能大于p
每次询问给出一个x,我们只能跳x的整数倍距离。

思路:

单调队列板子题
我们把x的所有倍数看成一个队列,处理成单调队列即可
时间复杂度 O(n+q×nx)O (n+q \times \frac{n}{x}) O(n+q×xn) 因为x的数量级和n一个级别,差不多可以抵消。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
#include<map>
#define first fi
#define second se
#define ios ios::sync_with_stdio(false); cin.tie(0);cout.tie(0);
#define endl '\n'
#define int long long  //  long long 才能过
using namespace std;
const int N=1e6+10;
int a[N],q[N],f[N];
int n,k,p;
signed main()
{
	ios;
	cin>>n>>k>>p;
	for(int i=1;i<=n;i++) cin>>a[i];
	while(k--)
	{
		int x;
		cin>>x;
		vector<int> b;  //  存所有x能到的地方 
		for(int i=0;i<=n;i+=x)
		{
			b.push_back(i);
		 } b.push_back(n+1);   // 多开一位,方便找最后一个区间中的最大值(就是答案) 
		 
		 int hh=0,tt=-1;
		 q[hh]=0;  // 初始化 ,相当于清空队列 
		 f[0]=0;  // 初始化优化,不用全部初始,我们每次计算时,先把下一位的f[]计算好,然后比较的时候不影响。 
		 for(int i=0;i<b.size();i++)
		 {
		 	while(hh<=tt && q[hh]<b[i]-p) hh++;
		 	f[b[i]]=f[q[hh]]+a[b[i]];    //  先更新该值,再去比较找单调队列。 
		 	while(hh<=tt && f[q[tt]]<f[b[i]]) tt--;
		 	q[++tt]=b[i];
		  } 
		  if(x>p) cout<<"Noob"<<endl;
		  else cout<<f[n+1]<<endl;
		
	}
	return 0;
}


2.CF Round #219 (Div. 1) C, Watching Fireworks is Fun

A festival will be held in a town’s main street. There are n sections in the main street. The sections are numbered 1 through n from left to right. The distance between each adjacent sections is 1.

In the festival m fireworks will be launched. The i-th (1 ≤ i ≤ m) launching is on time ti at section ai. If you are at section x(1 ≤ x ≤ n) at the time of i-th launching, you’ll gain happiness value bi − |ai − x| (note that the happiness value might be a negative value).

You can move up to d length units in a unit time interval, but it’s prohibited to go out of the main street. Also you can be in an arbitrary section at initial time moment (time equals to 1), and want to maximize the sum of happiness that can be gained from watching fireworks. Find the maximum total happiness.

Note that two or more fireworks can be launched at the same time.

Input
The first line contains three integers n,m,d(1 ≤ n ≤ 150000,1 ≤ m ≤ 300,1 ≤ d ≤ n).

Each of the next m lines contains integers ai,bi,ti(1 ≤ ai ≤ n,1 ≤ bi ≤ 109,1 ≤ ti ≤ 109)a_i,b_i,t_i(1 ≤ a_i ≤ n,1 ≤ b_i ≤ 10^9,1 ≤ t_i ≤ 10^9)ai,bi,ti(1 ain,1 bi 109,1 ti 109). The i-th line contains description of the i-th launching.

It is guaranteed that the condition ti ≤ ti + 1(1 ≤ i < m) will be satisfied.

Output
Print a single integer — the maximum sum of happiness that you can gain from watching all the fireworks.

Please, do not write the %lld specifier to read or write 64-bit integers in C++. It is preferred to use the cin, cout streams or the %I64d specifier.

Examples
input

50 3 1
49 1 1
26 1 4
6 1 10

output

-31

input

10 2 1
1 1000 4
9 1000 4

output

1992

题目大意

在一个街道上放烟花,有n个站点,距离为1,放m次烟花,在a位置放烟花,有b的幸福度 和放的时间t,当在i位置时,可以获得的幸福度是b−∣a−i∣b-|a-i|bai,并且单位时间可以移动d单位距离

思路

我们设状态为f[i] 表示在i位置上的幸福度
f[i]=max (f [j-D~j+D]) +b-|a-j|;
我们更加该方程来取单调队列

#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
#include<map>
#define first fi
#define second se
#define ios ios::sync_with_stdio(false); cin.tie(0);cout.tie(0);
using namespace std;
const int N=3e5+10;
int q[N];
long long g[N],f[N];// 在当前位置时的快乐最大值
int n,m,d;
long long a,b,t; 
int main()
{
	ios;
	scanf("%d%d%d",&n,&m,&d);
	int t1=1; // 上一次的时间 
	while(m--)
	{
		scanf("%lld%lld%lld",&a,&b,&t);
		int D=min(1ll*n,1ll*(t-t1)*d) ;t1=t;  //最大的移动距离 
		int hh=1,tt=0;
		for(int i=1;i<=D;i++)   // 求最左边D长度的最大值 
		{
			while(hh<=tt && g[q[tt]]<=g[i]) tt--;
			q[++tt]=i;
		}
		for(int i=1;i<=n;i++)  // 以当前位置求右边D长度 
		{
			if(i+D<=n)
			{
				while(hh<=tt && g[q[tt]]<=g[i+D]) tt--;
				q[++tt]=i+D;
			}
			while(hh<=tt && q[hh]<i-D) hh++;  //  避免左边长度超过D 
			f[i]=g[q[hh]]+b-abs(a-i);
		}
		memcpy(g,f,sizeof f);
	}
	long long ans=-1e18;
	for(int i=1;i<=n;i++) ans=max(ans,f[i]);
	printf("%lld",ans); 
	return 0;
}

3.NOI2005, 瑰丽华尔兹

你跳过华尔兹吗?当音乐响起,当你随着旋律滑动舞步,是不是有一种漫步仙境的惬意?

众所周知,跳华尔兹时,最重要的是有好的音乐。但是很少有几个人知道,世界上最伟大的钢琴家一生都漂泊在大海上,他的名字叫丹尼・布德曼・T.D.・柠檬・1900,朋友们都叫他 1900。

1900 在 20 世纪的第一年出生在往返于欧美的邮轮弗吉尼亚号上。很不幸,他刚出生就被抛弃,成了孤儿。1900 孤独的成长在弗吉尼亚号上,从未离开过这个摇晃的世界。也许是对他命运的补偿,上帝派可爱的小天使艾米丽照顾他。可能是天使的点化,1900 拥有不可思议的钢琴天赋:从未有人教,从没看过乐谱,但他却能凭着自己的感觉弹出最沁人心脾的旋律。当 1900 的音乐获得邮轮上所有人的欢迎时,他才 8 岁,而此时,他已经乘着海轮往返欧美大陆 50 余次了。

虽说是钢琴奇才,但 1900 还是个孩子,他有着和一般男孩一样的好奇和调皮,只不过更多一层浪漫的色彩罢了:这是一个风雨交加的夜晚,海风卷起层层巨浪拍打着弗吉尼亚号,邮轮随着巨浪剧烈的摇摆。船上的新萨克斯手迈克斯・托尼晕船了,1900 招呼托尼和他一起坐到舞厅里的钢琴上,然后松开了固定钢琴的闸,于是,钢琴随着海轮的倾斜滑动起来。准确的说,我们的主角 1900…

不妨认为舞厅是一个 N 行 M 列的矩阵,矩阵中的某些方格上堆放了一些家具,其他的则是空地。钢琴可以在空地上滑动,但不能撞上家具或滑出舞厅,否则会损坏钢琴和家具,引来难缠的船长。每个时刻,钢琴都会随着船体倾斜的方向向相邻的方格滑动一格,相邻的方格可以是向东、向西、向南或向北的。而艾米丽可以选择施魔法或不施魔法:如果不施魔法,则钢琴会滑动;如果施魔法,则钢琴会原地不动。

艾米丽是个天使,她知道每段时间的船体的倾斜情况。她想使钢琴在舞厅里滑行的路程尽量长,这样 1900 会非常高兴,同时也有利于治疗托尼的晕船。但艾米丽还太小,不会算,所以希望你能帮助她。

输入格式
输入文件的第一行包含 5 个数 N,M,x,y 和 K。N 和 M 描述舞厅的大小,x 和 y 为钢琴的初始位置;我们对船体倾斜情况是按时间的区间来描述的,且从 1 开始计算时间,比如 “在 [1,3] 时间里向东倾斜,[4,5] 时间里向北倾斜”,因此这里的 K 表示区间的数目。

以下 N 行,每行 M 个字符,描述舞厅里的家具。第 i 行第 j 列的字符若为 .,则表示该位置是空地,若为 x,则表示有家具。

以下 K 行,顺序描述 K 个时间区间,格式为:si ti di(1≤i≤K)。表示在时间区间 [si,ti] 内,船体都是向 di 方向倾斜的。di 为 1,2,3,4 中的一个,依次表示北、南、西、东(分别对应矩阵中的上、下、左、右)。输入保证区间是连续的,即s1=1,si=ti−1+1(1<i≤K),tK=T。

输出格式
输出文件仅有 1 行,包含一个整数,表示钢琴滑行的最长距离 (即格子数)。

样例输入1

4 5 4 1 3
..xx.
.....
...x.
.....
1 3 4
4 5 1
6 7 3

样例输出1

6

数据范围
50% 的数据中,1≤N,M≤200,T≤200。

100% 的数据中,1≤N,M≤200,K≤200,T≤40000。

思路

我们可以在四个方向进行单调队列,求其区间最大值。
f[x][y][t]=max(f[x][y][t-1],f[a][b][t-1])
f[x][y]=max(f[x][k]+y-k)=max(f[x][k]-k)+y (以向右为例 y-t<k<=y)
我们可以由此方程取单调队列

#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
#include<map>
#define first fi
#define second se
#define ios ios::sync_with_stdio(false); cin.tie(0);cout.tie(0);

using namespace std;
const int N=2e2+10;
int dx[]={0,-1,1,0,0},dy[]={0,0,0,-1,1};   //  方便一个函数同时处理四个方向 
int f[N][N];  //f[x][y][t]=max(f[x][y][t-1],f[a][b][t-1])
// f[x][y]=max(f[x][k]+y-k)=(f[x][k]-k)+y    (以向右为例  y-t<k<=y) 
char g[N][N];  //  存图 
struct T {int a,b;  // 单调队列,一个存答案,一个存走到的位置。 
}q[N];
int n,m,x,y,k;
int s,t,d;
int ans=0;
void solve(int x,int y,int t,int d,int n)
{
	int hh=1,tt=0;
	for(int i=1;i<=n;i++)  //  一共走的次数 
	{
		int v=f[x][y]-i;    //  进行比较的值 
		if(g[x][y]=='x') hh=1,tt=0;  //  当遇到阻碍,则队列被切断,队列重新开始。 
		else 
		{
			while(hh<=tt && q[hh].b<i-t) hh++;
			while(hh<=tt && q[tt].a<v) tt--;
			q[++tt]={v,i};
			f[x][y]=max(f[x][y],q[hh].a+i);
			ans=max(ans,f[x][y]);
		}
		x+=dx[d];   // 更新位置 
		y+=dy[d];
	}
}
signed main()
{
	ios;
	cin>>n>>m>>x>>y>>k;
	for(int i=1;i<=n;i++) cin>>g[i]+1;
	memset(f,-0x3f,sizeof f);
	f[x][y]=0;
	while(k--)
	{
		cin>>s>>t>>d;
		t=t-s+1;
		if(d==1) for(int i=1;i<=m;i++) solve(n,i,t,d,n);  
		else if(d==2) for(int i=1;i<=m;i++) solve(1,i,t,d,n);
		else if(d==3) for(int i=1;i<=n;i++) solve(i,m,t,d,m);
		else for(int i=1;i<=n;i++) solve(i,1,t,d,m);
	}
	cout<<ans<<endl;
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值