程序设计思维与实践 Week5 作业4道


本周主要是联系线性数据结构的应用。包括单调栈,单调队列,尺取法,前缀和与差分。

A - 最大矩形

给一个直方图,求直方图中的最大矩形的面积。例如,下面这个图片中直方图的高度从左到右分别是2, 1, 4, 5, 1, 3, 3, 他们的宽都是1,其中最大的矩形是阴影部分。
在这里插入图片描述

Input

输入包含多组数据。每组数据用一个整数n来表示直方图中小矩形的个数,你可以假定1 <= n <= 100000. 然后接下来n个整数h1, …, hn, 满足 0 <= hi <= 1000000000. 这些数字表示直方图中从左到右每个小矩形的高度,每个小矩形的宽度为1。 测试数据以0结尾。

Output

对于每组测试数据输出一行一个整数表示答案。

Sample Input

7 2 1 4 5 1 3 3
4 1000 1000 1000 1000
0

Sample Output

8
4000

分析

此题要用单调栈这种技巧。
先来了解一下单调栈的相关知识:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
其C++实现代码:(除此之外还可以用STL中的stack,这里是用数组表示栈)

for (int i = 0; i < n; i++) {
		while (top > 0 && st[top].value > a[i]) { 
			st1[st[top].index] = i;//st1记录高为原已知数列中st[top].index对应的值所对应的宽的右端点
			top--; //指针下移,弹栈
		}
		st[++top].value = a[i];//入栈
		st[top].index = i;//记录此时对应原数组中的下标
	}
单调栈的作用
  • 线性的时间复杂度
  • 单调递增栈 可以找到往左/往右第一个比当前元素
    元素
  • 单调递减栈 可以找到往左/往右第一个比当前元素
    元素
  • 可以求得以当前元素为最值的最大区间

标题该题的思路为:

在这里插入图片描述
由于数据范围为1 <= n <= 100000, 0 <= hi <= 1000000000,所以其面积得用long long类型,要初始化long long max1 = 0。

其中要特别注意long long数据类型的表达形式:
long long k = (long long) a[j] * (st1[j] - st2[j]);

C++

#include<iostream>
#include<algorithm>
using namespace std;
int a[100010];
int n;
int st1[100010];
int st2[100010];

struct Height{
	int value;
	int index;
};

void getRightMax() {
	int top = 0;
	Height *st = new Height[n + 1];
	for (int i = 0; i < n; i++) {
		while (top > 0 && st[top].value > a[i]) { 
			st1[st[top].index] = i;//st1记录高为原已知数列中st[top].index对应的值所对应的宽的右端点
			top--; //指针下移,弹栈
		}
		st[++top].value = a[i];//入栈
		st[top].index = i;//记录此时对应原数组中的下标
	}
	for (int i = 1; i <= top; i++) {
		st1[st[i].index] = n;//最终留在栈中的表示其右端点为n
	}
}
void getLeftMax() {
	int top = 0;
	Height *st = new Height[n + 1];
	for (int i = n-1; i >= 0; i--) {
		while (top > 0 && st[top].value > a[i]) {
			st2[st[top].index] = i + 1;//st1记录高为原已知数列中st[top].index对应的值所对应的宽的左端点,注意i+1
			top--;//指针下移,弹栈
		}
		st[++top].value = a[i];//入栈
		st[top].index = i;//记录此时对应原数组中的下标
	}
	for (int i = 1; i <= top; i++) {
		st2[st[i].index] = 0;//最终留在栈中的表示其左端点为0
	}
}
int main() {
	for (;;) {
		cin >> n;
		if (n == 0) break;
		for (int i = 0; i < n; i++) {
			cin >> a[i];
		}
		long long max1 = 0;
		getRightMax();
		getLeftMax();
		for (int j = 0; j < n; j++) {
			long long k = (long long) a[j] * (st1[j] - st2[j]);
			max1=max(k,max1);
		}
		cout << max1 << endl;
	}
	return 0;
}

B - TT’s Magic Cat

Thanks to everyone’s help last week, TT finally got a cute cat. But what TT didn’t expect is that this is a magic cat.

One day, the magic cat decided to investigate TT’s ability by giving a problem to him. That is select n cities from the world map, and a[i] represents the asset value owned by the i-th city.

Then the magic cat will perform several operations. Each turn is to choose the city in the interval [l,r] and increase their asset value by c. And finally, it is required to give the asset value of each city after q operations.

Could you help TT find the answer?

Input

The first line contains two integers n,q (1≤n,q≤2⋅105) — the number of cities and operations.

The second line contains elements of the sequence a: integer numbers a1,a2,…,an (−106≤ai≤106).

Then q lines follow, each line represents an operation. The i-th line contains three integers l,r and c (1≤l≤r≤n,−105≤c≤105) for the i-th operation.

Output

Print n integers a1,a2,…,an one per line, and ai should be equal to the final asset value of the i-th city.

Examples
InputInputInput
4 22 11 2
-3 6 8 45 -20
4 4 -21 2 41 1 -8
3 3 11 1 -6
OutputOutputOutput
-3 6 9 29 2-14
分析

本题主要是对前缀和与差分的练习。
先来了解一下前缀和:
在这里插入图片描述
差分:
在这里插入图片描述
针对该题:
在这里插入图片描述
至此代码实现相对简单。由于数据范围1≤n,q≤2⋅105,−106≤ai≤106,−105≤c≤105
此处也要用long long类型(最终得到的数组中的值可能为106+2·105·105,int的最大值为2147483647)。

C语言

#include<stdio.h>

long long a[200010];
long long b[200010];
int main() {
	long long n, q, sum = 0;
	long long l, r, c;
	scanf("%lld%lld", &n, &q);
	for (int i = 1; i <= n; i++) {
		scanf("%lld", &a[i]);
		if (i == 1)
			b[i] = a[i];
		else
			b[i] = a[i] - a[i - 1];
	}
	for (int j = 0; j < q; j++) {
		scanf("%lld%lld%lld", &l, &r, &c);
		b[l] += c;//差分
		b[r + 1] -= c;//差分
	}
	for (int k = 1; k <= n; k++) {
		sum += b[k];//记录b的前缀和
		a[k] = sum;//取到对应部分的前缀和
	}
	for (int i = 1; i <= n; i++) {
		if (i != n)
			printf("%lld ",a[i]);
		else
			printf("%lld\n",a[i]);
	}
	return 0;
}

C - 平衡字符串

一个长度为 n 的字符串 s,其中仅包含 ‘Q’, ‘W’, ‘E’, ‘R’ 四种字符。

如果四种字符在字符串中出现次数均为 n/4,则其为一个平衡字符串。

现可以将 s 中连续的一段子串替换成相同长度的只包含那四个字符的任意字符串,使其变为一个平衡字符串,问替换子串的最小长度?

如果 s 已经平衡则输出0。
请你帮帮他吧!

Input

一行字符表示给定的字符串s

Output

一个整数表示答案

Examples
Input

QWER

Output

0

Input

QQWE

Output

1

Input

QQQW

Output

2

Input

QQQQ

Output

3

Note

1<=n<=105

n是4的倍数

字符串中仅包含字符 ‘Q’, ‘W’, ‘E’ 和 ‘R’.

分析

此题是对尺取法的运用。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
针对本题的解题思路为:
在这里插入图片描述

C++

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<string.h> 
using namespace std;
char a[100010];
int main() {
	int left=0, right=0;
	int total;
	scanf("%s", &a);
	int res = strlen(a);
	int sum_1 = 0, sum_2 = 0, sum_3 = 0, sum_4 = 0;//主要是判断开始是否就平衡了 
	for (int i = 0; i < res; i++)
	{
		if (a[i] == 'Q')	sum_1++;
		else if (a[i] == 'W')	sum_2++;
		else if (a[i] == 'E')	sum_3++;
		else if (a[i] == 'R')	sum_4++;
	}
	if (sum_1 == sum_2 && sum_2 == sum_3 && sum_3 == sum_4)
	{
		cout << 0 << endl;//平衡结束 
		return 0;
	}
	while (right < strlen(a)) {
		int sum1 = 0, sum2 = 0, sum3 = 0, sum4 = 0;
		for (int i = left; i <= right; i++) {//被选中的各字母数量 
			if (a[i] == 81)sum1++;
			if (a[i] == 87)sum2++; 
			if (a[i] == 69)sum3++;
			if (a[i] == 82)sum4++;
		}
		sum1 = sum_1 - sum1;//用 sum1, sum2, sum3, sum4 分别记录不包含区间 [L, R]这一段时,字符 'Q', 'W', 'E', 'R' 的个数
		sum2 = sum_2 - sum2;
		sum3 = sum_3 - sum3;
		sum4 = sum_4 - sum4;
		int maxOf = max(max(max(sum1, sum2), sum3), sum4);
		total = right - left + 1;
		int free = total - (maxOf - sum1) - (maxOf - sum2) - (maxOf - sum3) - (maxOf - sum4);//先通过替换使 4 类字符数量一致
		if (free >= 0 && free % 4 == 0) {//判断是否还有剩余空闲,并且剩余空闲位置是否为 4 的倍数 
			res = min(total, res);
			if (left == right)
				left++, right++;//都得移动 
			else
				left++;//如果存在,从左边缩小区间 
		}
		else
			right++;//否则右移一格 
	}
	cout << res << endl;
	return 0;
}

D - 滑动窗口(C++和G++都交一下)

ZJM 有一个长度为 n 的数列和一个大小为 k 的窗口, 窗口可以在数列上来回移动. 现在 ZJM 想知道在窗口从左往右滑的时候,每次窗口内数的最大值和最小值分别是多少. 例如:
数列是 [1 3 -1 -3 5 3 6 7], 其中 k 等于 3.

Window positionMinimum valueMaximum value
[1 3 -1] -3 5 3 6 7-13
1 [3 -1 -3] 5 3 6 7-33
1 3 [-1 -3 5] 3 6 7-35
1 3 -1 [-3 5 3] 6 7-35
1 3 -1 -3 [5 3 6] 736
1 3 -1 -3 5 [3 6 7]37
Input

输入有两行。第一行两个整数n和k分别表示数列的长度和滑动窗口的大小,1<=k<=n<=1000000。第二行有n个整数表示ZJM的数列。

Output

输出有两行。第一行输出滑动窗口在从左到右的每个位置时,滑动窗口中的最小值。第二行是最大值。

Sample Input

8 3
1 3 -1 -3 5 3 6 7

Sample Output

-1 -3 -3 -3 3 3
3 3 5 5 6 7

分析

本题主要是对单调队列的运用。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
其用C++实现代码为:

//这是找局部区域内的最小值
for (int i = 0; i < n; i++) {
		while (head <= tail && a[queue[tail]]>a[i])
			tail--;//后一项比队列中的元素要小,队列中的元素出队
		queue[++tail] = i;//入队列
		if (queue[tail] - queue[head] >= k)
			head++;//这点一定要判断,如果最小值不在该区域内了则要弹出
		}
	}

本题的思路为:

  • 如果查找全局的最小值, 可以使用单调栈, 尽管有些多余
  • 现在要求查找窗口内的最小值, 是一个 局部 的概念
  1. 维护一个单调递增队列, 队列中的元素均要满足条件,属于当前窗口
  2. 要注意,当元素不属于当前窗口时, 将队首元素弹出即可
  3. 进行单调递增队列和单调递减队列共两次则可得出区域内的最大值和最小值
  4. 还有注意,队列中前k-1个元素是不需要输出的,其包含区域小于k

C++

#include<iostream>
#include<stdio.h>
using namespace std;
int n, k;
int a[1000010];
int queue[1000010];
void minOf() {
	int head = 0, tail = -1;
	for (int i = 0; i < n; i++) {
		while (head <= tail && a[queue[tail]]>a[i])
			tail--;//后一项比队列中的元素要小,队列中的元素出队
		queue[++tail] = i;//入队列
		if (queue[tail] - queue[head] >= k)
			head++;//这点一定要判断,如果最小值不在该区域内了则要弹出
		if (i >= k - 1) {//到了k个元素后可以直接输出,可以不用存储后再一起输出
			if (i == k - 1)
				cout << a[queue[head]];
			else
				cout << " " << a[queue[head]];
		}
	}
}

void maxOf() {
	int head = 0, tail = -1;
	for (int i = 0; i < n; i++) {
		while (head <= tail && a[queue[tail]] < a[i])
			tail--;//后一项比队列中的元素要大,队列中的元素出队
		queue[++tail] = i;//入队列
		if (queue[tail] - queue[head] >= k)
			head++;//如果最大值不在该区域内了则要弹出
		if (i >= k - 1) {
			if (i == k - 1)
				cout << a[queue[head]];
			else
				cout << " " << a[queue[head]];
		}
	}
}

int main() {
	cin >> n >> k;
	for (int i = 0; i < n; i++) {
		scanf("%d", &a[i]);
	}
	minOf();
	cout << endl;
	maxOf();
	cout << endl;
	return 0;
}

其中TE的话可以考虑将cin >> a[i];换成scanf("%d", &a[i]);

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值