A - 最大矩形
题意
给一个直方图,求图中最大矩形面积。例如,下面这个图片中直方图的高度从左到右分别是2, 1, 4, 5, 1, 3, 3, 他们的宽都是1,其中最大的矩形是阴影部分。
解题思路
- 对于求取最大矩形的面积,最简单的做法是对于直方图的每一个小矩形,求以这个小矩形为高延伸出的大矩形的面积,并取最大值。
- 而我们可以更进一步,若小矩形的高度是递增的,则我们可以认为最大矩形的右边界不在当前小矩形,直到找到第一个非递增的小矩形为止,前面的递增序列中才有可能存在最大的矩形面积。
- 从而,我们可以使用单调栈这一数据结构来维护一个单调递增序列,当遍历直方图遇见高度小于栈顶元素时,不断出栈,直到栈为空或栈顶元素小于当前高度。并在出栈过程中计算矩形的面积。
- 注: 0 < = h i < = 1000000000 , 0 <= hi <= 1000000000, 0<=hi<=1000000000, 所以最大矩形面积可能超过int,需要使用long long存储
代码
#include <iostream>
#include <stack>
#include <cmath>
#include <algorithm>
using namespace std;
long long h[100005];
int main()
{
int n;
while(cin >> n){
if(n == 0) break;
stack<long long> s;
long long ans = 0;
for(int i = 0; i < n; ++i) cin >> h[i];
h[n] = 0;
for(int i = 0; i <= n; ++i){
while(!s.empty() && h[s.top()] >= h[i]){
long long tmp = s.top();
s.pop();
if(s.empty())
tmp = i * h[tmp];
else
tmp = h[tmp] * (i - s.top() - 1);
ans = max(tmp, ans);
}
s.push(i);
}
cout << ans << endl;
memset(h, 0, sizeof(h));
}
return 0;
}
B - TT’s Magic Cat
题意
题目的大意是:给定 n n n个数,并对这 n n n个数进行 q q q次操作,每次操作对第 l l l到第 r r r这个区间的所有数加上 c c c,最后再依次输出修改后的这 n n n个数。
解题思路
- 这是一道差分构造的题目,通过差分数组使得原数组的区间修改变成差分数组的单点修改,以此降低复杂度。
- 然而这题的难点其实并不在差分,其实隐藏在数据范围,表面上
1
0
6
10^6
106级别的数据,实际上在经过
q
q
q次修改后最大能够达到
1
0
10
10^{10}
1010的级别,比int要大。
明明A题刚栽在long long,这题又栽了。orz
代码
#include <iostream>
using namespace std;
long long a[200010], b[200010];
int main()
{
int n,q;
cin >> n >> q;
for(int i = 1; i <= n; i++){
cin >> a[i];
if(i == 1)
b[i] = a[i];
else
b[i] = a[i] - a[i-1];
}
while(q--){
int l, r, c;
cin >> l >> r >> c;
b[l] += c;
b[r+1] -= c;
}
long long sum = 0;
for(int i = 1; i <= n; i++){
sum += b[i];
cout << sum <<' ';
}
cout << endl;
return 0;
}
C - 平衡字符串
题意
- 一个长度为 n 的字符串 s,其中仅包含 ‘Q’, ‘W’, ‘E’, ‘R’ 四种字符。
- 如果四种字符在字符串中出现次数均为 n/4,则其为一个平衡字符串。
- 现可以将 s 中连续的一段子串替换成相同长度的只包含那四个字符的任意字符串,使其变为一个平衡字符串,问替换子串的最小长度?
- 如果 s 已经平衡则输出0。
解题思路
- 使用map来统计每个字符的出现次数,若4个字符出现次数相等,则输出0,不相等的情况使用尺取法来确定最小区间。
- 如何判断替换当前子串能否使字符串平衡? 我们知道,平衡时字符串中每个字符均出现 n 4 \frac n 4 4n次,那么只需选中区间之外,四种字符的出现次数都小于等于 n 4 \frac n 4 4n次即可。
代码
#include <iostream>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <map>
using namespace std;
long long h[100005];
char str[10005];
map<char,int>mp;
int main()
{
cin >> str;
int len = strlen(str);
int l = 0, r = 0;
int ans = len;
for(int i = 0; i < len; i++){
mp[str[i]]++;
}
if(mp['Q'] == mp['W'] && mp['E'] == mp['Q'] && mp['R'] == mp['Q']){
cout << 0 <<endl;
return 0;
}
while(r < len){
mp[str[r]]--;
while(l <= r && mp['Q'] <= len/4 && mp['W'] <= len/4 && mp['E'] <= len/4 && mp['R'] <= len/4){
ans = min(ans, r-l+1);
mp[str[l]]++;
if(l == r)
l++,r++;
else
l++;
}
r++;
}
cout << ans << endl;
return 0;
}
D - 滑动窗口
题意
ZJM 有一个长度为 n 的数列和一个大小为 k 的窗口, 窗口可以在数列上来回移动. 现在 ZJM 想知道在窗口从左往右滑的时候,每次窗口内数的最大值和最小值分别是多少.
解题思路
使用单调递增/递减队列来存储当前窗口上的数,每次移动窗口时判断队首是否在当前窗口,若在则为当前窗口的最小值/最大值
代码
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <deque>
using namespace std;
int a[1000005];
deque<int> q;
int main()
{
int n,k;
cin >> n >> k;
for(int i = 0; i < n; i++){
scanf("%d",&a[i]);
}
for(int i = 0; i < k; i++){
if(q.empty() || a[q.back()] <= a[i])
q.push_back(i);
else{
while(!q.empty() && a[q.back()] > a[i]){
q.pop_back();
}
q.push_back(i);
}
}
for(int i = k; i < n; i++){
cout << a[q.front()] << ' ';
if(q.front() <= i - k)
q.pop_front();
if(q.empty() || a[q.back()] <= a[i])
q.push_back(i);
else{
while(!q.empty() && a[q.back()] > a[i]){
q.pop_back();
}
q.push_back(i);
}
}
cout << a[q.front()] << endl;
q.clear();
for(int i = 0; i < k; i++){
if(q.empty() || a[q.back()] >= a[i])
q.push_back(i);
else{
while(!q.empty() && a[q.back()] < a[i]){
q.pop_back();
}
q.push_back(i);
}
}
for(int i = k; i < n; i++){
cout << a[q.front()] << ' ';
if(q.front() <= i - k)
q.pop_front();
if(q.empty() || a[q.back()] >= a[i])
q.push_back(i);
else{
while(!q.empty() && a[q.back()] < a[i]){
q.pop_back();
}
q.push_back(i);
}
}
cout << a[q.front()] << endl;
return 0;
}