单调队列优化DP


最大子序和

输入一个长度为 n 的整数序列,从中找出一段长度不超过 m 的连续子序列,使得子序列中所有数的和最大。

注意: 子序列的长度至少是 1。

输入格式
第一行输入两个整数 n,m。

第二行输入 n 个数,代表长度为 n 的整数序列。

同一行数之间用空格隔开。

输出格式
输出一个整数,代表该序列的最大子序和。

数据范围
1 ≤ n,m ≤ 300000

输入样例:

6 4
1 -3 5 1 -2 3

输出样例:

7

在这里插入图片描述
在这里插入图片描述

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 300010;
int n, m;
int res = -0x3f3f3f3f;
int a[N], q[N];
int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i ++ ) {
        cin >> a[i];
        a[i] += a[i - 1];
    }
    int hh = 0, tt = 0;
    for (int i = 1; i <= n; i ++ ) {
        if (q[hh] < i - m) hh ++ ;
        res = max(res, a[i] - a[q[hh]]);
        while (hh <= tt && a[q[tt]] >= a[i]) tt -- ;
        q[ ++ tt] = i;
    } 
    cout << res << endl;
    return 0;
}

笔记学习:
作者:彩色铅笔
链接:https://www.acwing.com/solution/content/67527/
来源:AcWing
作者:Bug-Free
链接:https://www.acwing.com/solution/content/28015/
来源:AcWing


修剪草坪

在一年前赢得了小镇的最佳草坪比赛后,FJ 变得很懒,再也没有修剪过草坪。

现在,新一轮的最佳草坪比赛又开始了,FJ 希望能够再次夺冠。

然而,FJ 的草坪非常脏乱,因此,FJ 只能够让他的奶牛来完成这项工作。

FJ 有 N 只排成一排的奶牛,编号为 1 到 N。

每只奶牛的效率是不同的,奶牛 i 的效率为 Ei

编号相邻的奶牛们很熟悉,如果 FJ 安排超过 K 只编号连续的奶牛,那么这些奶牛就会罢工去开派对。

因此,现在 FJ 需要你的帮助,找到最合理的安排方案并计算 FJ 可以得到的最大效率。

注意,方案需满足不能包含超过 K 只编号连续的奶牛。

输入格式
第一行:空格隔开的两个整数 N 和 K;

第二到 N+1 行:第 i+1 行有一个整数 Ei

输出格式
共一行,包含一个数值,表示 FJ 可以得到的最大的效率值。

数据范围
1 ≤ N ≤ 105,
0 ≤ Ei ≤ 109

输入样例:

5 2
1
2
3
4
5

输出样例:

12

样例解释
FJ 有 5 只奶牛,效率分别为 1、2、3、4、5。

FJ 希望选取的奶牛效率总和最大,但是他不能选取超过 2 只连续的奶牛。

因此可以选择第三只以外的其他奶牛,总的效率为 1 + 2 + 4 + 5 = 12。

#include <iostream>	
using namespace std;
typedef long long LL;
const int N = 1e5 + 10;
int n, m;
LL s[N];
LL f[N];
int q[N];
LL g(int i) 
{
	if (!i) return 0;
	return f[i - 1] - s[i];
}
int main()
{
	cin >> n >> m;
	for (int i = 1; i <= n; i ++ ) 
	{
		cin >> s[i];
		s[i] += s[i - 1];
	}
	int hh = 0, tt = 0;
	for (int i = 1; i <= n; i ++ ) 
	{
		if (q[hh] < i - m) hh ++ ;
		f[i] = max(f[i - 1], g(q[hh]) + s[i]);
		while (hh <= tt && g(q[tt]) <= g(i)) tt -- ;
		q[ ++ tt] = i;
	}
	cout << f[n] << endl;
	return 0; 
} 

旅行问题

John 打算驾驶一辆汽车周游一个环形公路。

公路上总共有 n 个车站,每站都有若干升汽油(有的站可能油量为零),每升油可以让汽车行驶一千米。

John 必须从某个车站出发,一直按顺时针(或逆时针)方向走遍所有的车站,并回到起点。

在一开始的时候,汽车内油量为零,John 每到一个车站就把该站所有的油都带上(起点站亦是如此),行驶过程中不能出现没有油的情况。

任务:判断以每个车站为起点能否按条件成功周游一周。

输入格式
第一行是一个整数 n,表示环形公路上的车站数;

接下来 n 行,每行两个整数 pi,di,分别表示表示第 i 号车站的存油量和第 i 号车站到 顺时针方向 下一站的距离。

输出格式
输出共 n 行,如果从第 i 号车站出发,一直按顺时针(或逆时针)方向行驶,能够成功周游一圈,则在第 i 行输出 TAK,否则输出 NIE。

数据范围
3 ≤ n ≤ 106,
0 ≤ pi ≤ 2×109,
0 ≤ di ≤ 2×109

输入样例:

5
3 1
1 2
5 2
0 1
5 4

输出样例:

TAK
NIE
TAK
NIE
TAK
#include <iostream>
using namespace std;
typedef long long LL;
const int N = 2e6 + 10;
int n;
int oil[N], dist[N];
LL s[N];
int q[N];
bool ans[N];
int main()
{
	cin >> n;
	for (int i = 1; i <= n; i ++ ) 
	{
		cin >> oil[i] >> dist[i];
		s[i] = s[i + n] = oil[i] - dist[i];	
	}
	for (int i = 1; i <= n * 2; i ++ ) s[i] += s[i - 1];
	// 正序 
	int hh = 0, tt = 0;
	q[0] = n * 2 + 1;
	for (int i = n * 2; i >= 0; i -- ) 
	{
		if (q[hh] > i + n) hh ++ ;
		if (i < n) 
		{
			if (s[i] <= s[q[hh]]) ans[i + 1] = true;	
		}	
		while (hh <= tt && s[q[tt]] >= s[i]) tt -- ;
		q[ ++ tt] = i;
	} 
	// 逆序 
	dist[0] = dist[n];
	for (int i = 1; i <= n; i ++ ) s[i] = s[i + n] = oil[i] - dist[i - 1];
	for (int i = 1; i <= n * 2; i ++ ) s[i] += s[i - 1];
	
	hh = 0, tt = 0;
	q[0] = 0;
	for (int i = 1; i <= n * 2; i ++ )
	{
		if (q[hh] < i - n) hh ++ ;
		if (i > n)
		{
			if (s[i] >= s[q[hh]]) ans[i - n] = true;
		}
		while (hh <= tt && s[q[tt]] <= s[i]) tt -- ;
		q[ ++ tt] = i;	
	} 
	
	for (int i = 1; i <= n; i ++ )
		if (ans[i]) puts("TAK");
		else puts("NIE");
	return 0;
}

烽火传递

烽火台是重要的军事防御设施,一般建在交通要道或险要处。

一旦有军情发生,则白天用浓烟,晚上有火光传递军情。

在某两个城市之间有 n 座烽火台,每个烽火台发出信号都有一定的代价。

为了使情报准确传递,在连续 m 个烽火台中至少要有一个发出信号。

现在输入 n,m 和每个烽火台的代价,请计算在两城市之间准确传递情报所需花费的总代价最少为多少。

输入格式
第一行是两个整数 n,m,具体含义见题目描述;

第二行 n 个整数表示每个烽火台的代价 ai。

输出格式
输出仅一个整数,表示最小代价。

数据范围
1 ≤ n,m ≤ 2×105,
0 ≤ ai ≤ 1000

输入样例:

5 3
1 2 5 6 2

输出样例:

4
#include <iostream>
using namespace std;
const int N = 2e5 + 10, INF = 1e9;
int n, m;
int w[N], q[N];
int f[N];  
int main()
{
	cin >> n >> m;
	for (int i = 1; i <= n; i ++ ) cin >> w[i];
	int hh = 0, tt = 0;
	for (int i = 1; i <= n; i ++ ) 
	{
		if (q[hh] < i - m) hh ++ ;
		f[i] = f[q[hh]] + w[i];
		while (hh <= tt && f[q[tt]] >= f[i]) tt -- ;
		q[ ++ tt] = i;
	}
	int res = INF;
	for (int i = n - m + 1; i <= n; i ++ ) res = min(res, f[i]);
	cout << res << endl;
	return 0;
} 	

绿色通道

高二数学《绿色通道》总共有 n 道题目要抄,编号 1,2,…,n,抄第 i 题要花 ai 分钟。

小 Y 决定只用不超过 t 分钟抄这个,因此必然有空着的题。

每道题要么不写,要么抄完,不能写一半。

下标连续的一些空题称为一个空题段,它的长度就是所包含的题目数。

这样应付自然会引起马老师的愤怒,最长的空题段越长,马老师越生气。

现在,小 Y 想知道他在这 t 分钟内写哪些题,才能够尽量减轻马老师的怒火。

由于小 Y 很聪明,你只要告诉他最长的空题段至少有多长就可以了,不需输出方案。

输入格式
第一行为两个整数 n,t。

第二行为 n 个整数,依次为 a1,a2,…,an

输出格式
输出一个整数,表示最长的空题段至少有多长。

数据范围
0 < n ≤ 5×104,
0 < ai ≤ 3000,
0 < t ≤ 108

输入样例:

17 11
6 4 5 2 5 3 4 5 2 3 4 5 2 3 6 3 5

输出样例:

3
#include <iostream>
using namespace std;
const int N = 50010, INF = 1e9;
int n, m;
int w[N];
int f[N], q[N];
bool check(int k)
{
	f[0] = 0;
	int hh = 0, tt = 0;
	for (int i = 1; i <= n; i ++ )
	{
		if (hh <= tt && q[hh] < i - k - 1) hh ++ ;
		f[i] = f[q[hh]] + w[i];
		while (hh <= tt && f[q[tt]] >= f[i]) tt -- ;
		q[ ++ tt] = i; 
	}
	int res = INF;
	for (int i = n - k; i <= n; i ++ ) res = min(res, f[i]);
	return res <= m;
}
int main()
{
	cin >> n >> m;
	for (int i = 1; i <= n; i ++ ) cin >> w[i];
	int l = 0, r = n;
	while (l < r)
	{
		int mid = l + r >> 1;
		if (check(mid)) r = mid;
		else l = mid + 1;
	}
	cout << r << endl;
	return 0;
}

理想的正方形

有一个 a×b 的整数组成的矩阵,现请你从中找出一个 n×n 的正方形区域,使得该区域所有数中的最大值和最小值的差最小。

输入格式
第一行为三个整数,分别表示 a,b,n 的值;

第二行至第 a+1 行每行为 b 个非负整数,表示矩阵中相应位置上的数。

输出格式
输出仅一个整数,为 a×b 矩阵中所有“n×n 正方形区域中的最大整数和最小整数的差值”的最小值。

数据范围
2 ≤ a,b ≤ 1000,
n ≤ a,n ≤ b,n ≤ 100,
矩阵中的所有数都不超过 109。

输入样例:

5 4 2
1 2 5 6
0 17 16 0
16 17 2 1
2 10 2 1
1 2 2 2

输出样例:

1
#include <iostream>
using namespace std;
const int N = 1010, INF = 1e9;
int n, m, k;
int w[N][N];
int row_min[N][N], row_max[N][N];
int q[N];
void get_min(int a[], int b[], int tot)
{
	int hh = 0, tt = -1; 
	for (int i = 1; i <= tot; i ++ )
	{
		if (hh <= tt && q[hh] <= i - k) hh ++ ;
		while (hh <= tt && a[q[tt]] >= a[i]) tt -- ;
		q[ ++ tt] = i;
		b[i] = a[q[hh]];
	}
}
void get_max(int a[], int b[], int tot)
{
	int hh = 0, tt = -1; 
	for (int i = 1; i <= tot; i ++ )
	{
		if (hh <= tt && q[hh] <= i - k) hh ++ ;
		while (hh <= tt && a[q[tt]] <= a[i]) tt -- ;
		q[ ++ tt] = i;
		b[i] = a[q[hh]];
	}
}
int main()
{
	cin >> n >> m >> k;
	for (int i = 1; i <= n; i ++ )
		for (int j = 1; j <= m; j ++ )
			cin >> w[i][j];
	for (int i = 1; i <= n; i ++ )
	{
		get_min(w[i], row_min[i], m);
		get_max(w[i], row_max[i], m);
	}
	int res = INF;
	int a[N], b[N], c[N];
	for (int i = k; i <= m; i ++ )
	{
		for (int j = 1; j <= n; j ++ ) a[j] = row_min[j][i];
		get_min(a, b, n);
		for (int j = 1; j <= n; j ++ ) a[j] = row_max[j][i];
		get_max(a, c, n);
		for (int j = k; j <= n; j ++ ) res = min(res, c[j] - b[j]);
	}
	cout << res << endl;
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值