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
Input | Input | Input |
---|---|---|
4 2 | 2 1 | 1 2 |
-3 6 8 4 | 5 -2 | 0 |
4 4 -2 | 1 2 4 | 1 1 -8 |
3 3 1 | 1 1 -6 |
Output | Output | Output |
---|---|---|
-3 6 9 2 | 9 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 position | Minimum value | Maximum value |
---|---|---|
[1 3 -1] -3 5 3 6 7 | -1 | 3 |
1 [3 -1 -3] 5 3 6 7 | -3 | 3 |
1 3 [-1 -3 5] 3 6 7 | -3 | 5 |
1 3 -1 [-3 5 3] 6 7 | -3 | 5 |
1 3 -1 -3 [5 3 6] 7 | 3 | 6 |
1 3 -1 -3 5 [3 6 7] | 3 | 7 |
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++;//这点一定要判断,如果最小值不在该区域内了则要弹出
}
}
本题的思路为:
- 如果查找全局的最小值, 可以使用单调栈, 尽管有些多余
- 现在要求查找窗口内的最小值, 是一个 局部 的概念
- 维护一个单调递增队列, 队列中的元素均要满足条件,属于当前窗口
- 要注意,当元素不属于当前窗口时, 将队首元素弹出即可
- 进行单调递增队列和单调递减队列共两次则可得出区域内的最大值和最小值
- 还有注意,队列中前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]);