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
解题思路
**矩形面积的构成:**确定的高度,长度左端点为往左数第一个小于此高度的点,长度右端点为往右数第一个小于此高度的点;
获得每个确定高度的左右端点的方法:
结构体 rectangle 包含左端点left,右端点right,高度height,在高度数组中的编号number;初始化的时候其左右端点均置0,高度和编号取决于输入;
获取右端点:从高度数组从头向尾进行遍历,将数据存储在一个单调递减栈内,具体做法就是,往栈内存数据,假如这个高度要比栈顶的高度要小,那么栈顶高度对应的矩形的左端点就等于要入栈的这个高度的编号,然后将栈顶元素就行出栈之后,再将这个元素入栈;
获取左端点:大致于获取右端点的方法相同,只是遍历的顺序变成从尾向头遍历;
面积就等于每个结构体内,heigh * (right - left);
解题代码
#include <iostream>
#include <vector>
#include <stack>
#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;
struct rectangle {
long long int left;
long long int right;
long long int height;
long long int number;
rectangle() {left = 0; right = 0; height = 0; number = 0;}
long long int area() {return height * (right - left);}
};
vector<rectangle> jx;
stack<rectangle> chose;
int n;
long long int h;
long long int s = 0;
int main()
{
while(cin>>n){
if (n == 0) {return 0;}
else {
//矩形初始化
jx.clear();
for (int a = 0; a < n; a++) {
rectangle r;
cin>>h;
r.height = h;
r.number = a;
jx.push_back(r);
}
//确定区间长度
//确定右节点
for (int a = 0; a < n; a++) {
while (!chose.empty() && chose.top().height > jx[a].height ) {
jx[chose.top().number].right = a;
chose.pop();
}
chose.push(jx[a]);
}
while (!chose.empty()) {
jx[chose.top().number].right = n;
chose.pop();
}
//确定左节点
for (int a = n - 1; a >= 0; a--) {
while (!chose.empty() && chose.top().height > jx[a].height ) {
jx[chose.top().number].left = a + 1;
if (s <= jx[chose.top().number].area()) s = jx[chose.top().number].area();
chose.pop();
}
chose.push(jx[a]);
}
while (!chose.empty()) {
jx[chose.top().number].left = 0;
if (s <= jx[chose.top().number].area()) s = jx[chose.top().number].area();
chose.pop();
}
cout<<s<<endl;
s = 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?
关键操作:每轮选择[ l,r ]区间内的城市,并将其资产价值增加c。最后给出q次操作后各城市的资产价值。
Input
The first line contains two integers n,q (1 ≤ n,q ≤ 2 * 10 ^ 5) — the number of cities and operations.
The second line contains elements of the sequence a: integer numbers a1,a2,…,an (−10 ^ 6 ≤ ai ≤ 10 ^ 6).
Then q lines follow, each line represents an operation. The i-th line contains three integers l,r and c (1 ≤ l ≤ r ≤ n,−10 ^ 5 ≤ c ≤ 10 ^ 5) for the i-th operation.
译文:
第一行包含两个整数n,q(1 ≤ n,q ≤ 2*10 ^ 5) —— 城市的数量和操作。
第二行包含序列a的元素:整数a1,a2,…,an(−10 ^ 6 ≤ a_i ≤ 10 ^ 6)。
接下来是q行,每一行代表一个操作。第 i 行包含3个整数l,r和c(1 ≤ l ≤ r ≤ n,−10 ^ 5 ≤ c ≤ 10 ^ 5)。
Output
Print n integers a1,a2……,an one per line, and a_i should be equal to the final asset value of the i-th city.
译文:
打印n个整数a1,a2……an 每行1个,a_i应该等于第i个城市的最终资产价值。
Sample Input
4 2
-3 6 8 4
4 4 -2
3 3 1
Sample Output
-3 6 9 2
解题思路
由于题中要进行的操作都是对于某个区间的城市资产价格进行改变,可以
将原城市资产价格数组转换成一个差分数组,然后将所有的区间修改转换成差分数组中的单点修改;
再由修改后的差分数组求变换后的资产价格数组;
解题代码
#include <iostream>
#include <vector>
#include <string>
#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;
int n, q, l, r, c;
long long int a[200005], x[200005], b[200005];
int main()
{
cin>>n>>q;
//原数组初始化
for (int w = 0; w < n; w++) {
long long int number;
cin>>number;
a[w] = number;
//前缀和数组初始化
if (w == 0) x[0] = a[0];
else x[w] = a[w] - a[w - 1];
}
//将原数组的区间加 转换成 差分数组的单点修改
for (int w = 0; w < q; w++) {
cin>>l>>r>>c;
x[l - 1] += c;
x[r] -= c;
}
//变化之后的数组获得
b[0] = x[0];
for (int e = 1; e < n; e++) {
b[e] = b[e - 1] + x[e];
}
//输出
for (int w = 0; w < n; w++) {
cout<<b[w]<<" ";
}
return 0;
}
C — 平衡字符串 (尺取法)
题目描述
一个长度为 n 的字符串 s,其中仅包含 ‘Q’, ‘W’, ‘E’, ‘R’ 四种字符。
如果四种字符在字符串中出现次数均为 n/4,则其为一个平衡字符串。
现可以将 s 中连续的一段子串替换成相同长度的只包含那四个字符的任意字符串,使其变为一个平衡字符串,问替换子串的最小长度?
如果 s 已经平衡则输出0。
Input
一行字符表示给定的字符串s
Output
一个整数表示答案
Sample Input
QQWE
Sample Output
1
解题思路
对于整个的字符串,选取其中的[ left,right ]区间进行判断,标准是:
(1)整个字符串中除去选择的区间之后,记录字符Q、W、E、R的数量分别记为sum1~4;
(2)设 max = max(sum1~4);total = right - left + 1;free = total - ((max-sum1)+(max-sum2)+(max-sum3)+(max-sum4));
(3)假如free >= 0 且 free % 4 == 0
则满足条件;
当所选区间[ left,right ]满足要求,那么尝试将区间变小一点,即left++,并根据再判断结果对ans进行更新,ans = min(ans,right - left* + 1);
当所选区间[ left,right ]不满足要求,那么尝试将区间再变大,即right++;
上述对区间进行修改的操作,始终在right < sizeof(字符串);
解题代码
#include <iostream>
#include <vector>
#include <string>
#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;
int sum1 = 0, sum2 = 0, sum3 = 0, sum4 = 0; //Q W E R
bool judge(int l, int r) {
int m = max(max(sum1, sum2), max(sum3, sum4));
int t = r - l + 1;
int f = t - ((m - sum1) + (m - sum2) + (m - sum3) + (m - sum4));
if (f >= 0 && f % 4 == 0) return true;
else return false;
}
int main()
{
string z;
cin>>z;
long long int n = z.size();
long long int ans = n;
for (int a = 0; a < n; a++) {
if (z[a] == 'Q') sum1++;
if (z[a] == 'W') sum2++;
if (z[a] == 'E') sum3++;
if (z[a] == 'R') sum4++;
}
if (sum1 == n / 4 && sum2 == n / 4 && sum3 == n / 4 && sum4 == n / 4) {
cout<<ans<<endl;
return 0;
}
int left = 0, right = 0;
if (z[0] == 'Q') sum1--;
if (z[0] == 'W') sum2--;
if (z[0] == 'E') sum3--;
if (z[0] == 'R') sum4--;
while (right < n) {
//当前[left, right]满足条件,left++
if (judge(left, right)) {
//对ans值进行替换
if (right - left + 1 < ans) ans = right - left + 1;
if (left == right) {
//[left, right]区间扩大,故sum--
right++;
if (z[right] == 'Q') sum1--;
if (z[right] == 'W') sum2--;
if (z[right] == 'E') sum3--;
if (z[right] == 'R') sum4--;
}
//[left, right]区间缩小,故sum++
if (z[left] == 'Q') sum1++;
if (z[left] == 'W') sum2++;
if (z[left] == 'E') sum3++;
if (z[left] == 'R') sum4++;
left++;
}
//当前区间不满足条件,righr++
else {
right++;
if (z[right] == 'Q') sum1--;
if (z[right] == 'W') sum2--;
if (z[right] == 'E') sum3--;
if (z[right] == 'R') sum4--;
}
}
cout<<ans<<endl;
return 0;
}
D — 滑动窗口(单调队列)
题目描述
ZJM 有一个长度为 n 的数列和一个大小为 k 的窗口, 窗口可以在数列上来回移动. 现在 ZJM 想知道在窗口从左往右滑的时候,每次窗口内数的最大值和最小值分别是多少. 例如:
数列是 [1 3 -1 -3 5 3 6 7], 其中 k 等于 3.
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
解题思路
获取滑动过程中每次的最小值:用单调递增队列实现
首先对首元素a[0~2],保证每次入队之后,队首元素要是三个元素中最小的那个,在入队的时候假如队首元素的值要大于要入队元素,则需要先将队首元素进行出队,然后入队,
之后每次往下一个元素进行比较,首先判断是否满足单调递增的队列性质,然后判断队首元素是否在规定的区间内(这两个交换顺序也可以);
获取滑动过程中每次的最大值:用单调递减队列实现
具体实现过程也与获取最小值相似,只是单调队列的性质变为单调递减的。
解题代码
#include <iostream>
#include <vector>
#include <deque>
#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;
int n, k;
deque<int> d;
int v[1000002];
int readin(){
int yi = 0, ri = 1;
char c = getchar();
while (!isdigit(c)){
if(c == '-') ri = -1;
c = getchar();
}
for (; isdigit(c); c = getchar()){
yi = yi * 10 + c - '0';
}
return yi * ri;
}
int main()
{
n = readin();
k = readin();
//区间的初始化
int z;
for (int a = 0; a < n; a++) {
z = readin();
v[a] = z;
}
//获取最小值
for (int a = 0; a < n; a++) {
while (!d.empty() && v[d.back()] > v[a]) {
d.pop_back();
}
d.push_back(a);
while (!d.empty() && a - d.front() > k - 1 ) {
d.pop_front();
}
if (a >= k - 1) cout<<v[d.front()]<<" ";
}
cout<<endl;
d.clear();
//获取最大值
for (int a = 0; a < n; a++) {
while (!d.empty() && v[d.back()] < v[a]) {
d.pop_back();
}
d.push_back(a);
while (!d.empty() && a - d.front() > k - 1 ) {
d.pop_front();
}
if (a >= k - 1) cout<<v[d.front()]<<" ";
}
d.clear();
return 0;
}