A - 最大矩形 HDU - 1506
题目描述
给一个直方图,求直方图中的最大矩形的面积。例如,下面这个图片中直方图的高度从左到右分别是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
题解
- 思路:对于每一个柱形的高找到最优的面积,即左右都达到最宽。朴素方法为从此柱形开始左右都寻找一遍复杂度是n^2,而单调栈,可以在遍历的过程中,通过维护一个单调关系,当单调不合法时,记录弹出元素的左右端点,将复杂度降为O(n).
- 具体实现为:左右都跑一边,记录每个柱形的最左和最右,最后高度乘以r-l+1取所有max,就是最后答案。
代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <stack>
using namespace std;
typedef long long LL;
const int MAXN = 100000+10;
int h[MAXN], l[MAXN], r[MAXN];
int main() {
int n;
while (cin >> n) {
if (!n) break;
for (int i = 0; i < n; ++i) {
scanf("%d", &h[i]);
}
stack<int> s;
s.push(0);
for (int i = 1; i < n; ++i) {
while (s.size() && h[i] < h[s.top()]) {
r[s.top()] = i;
s.pop();
}
s.push(i);
}
while (s.size()) {
r[s.top()] = n;
s.pop();
}
s.push(n-1);
for (int i = n-2; i >= 0; --i) {
while (s.size() && h[i] < h[s.top()]) {
l[s.top()] = i;
s.pop();
}
s.push(i);
}
while (s.size()) {
l[s.top()] = -1;
s.pop();
}
LL ans = 0;
for (int i = 0; i < n; ++i) {
// cout << l[i] << " " << r[i] << endl;
ans = max(ans, (LL)((LL)h[i]*(LL)(r[i]-l[i]-1)));
}
cout << ans << endl;
}
return 0;
}
B - TT’s Magic Cat Gym - 272542A
题目描述
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
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.
Sample Input 1
4 2
-3 6 8 4
4 4 -2
3 3 1
Sample Output 1
-3 6 9 2
Sample Input 2
2 1
5 -2
1 2 4
Sample Output 2
9 2
Sample Input 3
1 2
0
1 1 -8
1 1 -6
Sample Output 3
-14
题解
- 思路:每次操作如果暴力遍历复杂度是
n^2
不可行,考虑到每次操作都是对连续区间的加减操作我们可以考虑:能否快速的把一次修改区间的操作表示出来?因为每次操作都是独立的,而且加减操作没有顺序性,我们完全可以分别记录然后最后累加所有的状态。另一方面,对于一个区间内的状态,我们记录一个标记,这个标记当遍历到区间左端点开启,右端点关闭,这样就O(1)记录了整个区间的变化。 - 实现:先根据输入给区间加标记,即左闭为变化值,右开抵消变化值,最后取标记的前缀和即可。
代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <stack>
using namespace std;
typedef long long LL;
const int MAXN = 200000+10;
LL a[MAXN], c[MAXN];
int main() {
int n, q;
cin >> n >> q;
for (int i = 1; i <= n; ++i) {
scanf("%lld", &a[i]);
}
while (q--) {
int l, r;
LL ci;
scanf("%d%d%lld", &l, &r, &ci);
c[l] += ci;
c[r+1] -= ci;
}
c[0] = 0;
for (int i = 1; i <= n; ++i) {
c[i] += c[i-1];
printf("%lld ", a[i]+c[i]);
}
return 0;
}
C - 平衡字符串 Gym - 270737B
题目描述
一个长度为 n 的字符串 s,其中仅包含 ‘Q’, ‘W’, ‘E’, ‘R’ 四种字符。
如果四种字符在字符串中出现次数均为 n/4,则其为一个平衡字符串。
现可以将 s 中连续的一段子串替换成相同长度的只包含那四个字符的任意字符串,使其变为一个平衡字符串,问替换子串的最小长度?
如果 s 已经平衡则输出0。
input
一行字符表示给定的字符串s
Output
一个整数表示答案
Example
Input
QWER
Output
0
Input
QQWE
Output
1
Input
QQQW
Output
2
Input
QQQQ
Output
3
题解
- 对于已知的 qwer 的个数,想要变成合法的序列,那么我们需要把小的部分像大的部分标齐,而我们判断一个区间是否能用来把qwer标齐只需要判断其个数是否能够达到用来填补的个数。这样在区间遍历的过程中很简单的就可以维护qwer的个数,又能得知当前区间的长度。所以可以用尺取法。
- 实现:qwer分别记录「除了当前选中的区间」之外qwer的个数。采用尺取法的两个步骤:1.右端点往右找到第一个合法的右端点,2.左端点不断朝右缩小,直到满足合法情况长度最小,记录答案即可。判断合法的过程:计算当前区间长度与区间外需要的填补的长度,如果大于且能够是4的倍数则合法。4的倍数的原因:填补之后如果有剩余,刚好再增加一组qwer。
代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <stack>
using namespace std;
const int MAXN = 100000+10;
char s[MAXN];
int q, w, e, r;
bool check(int L, int R) {
// cout << "--------- " << l << "," << r << endl;
// cout << "qwer" << q << " " << w << " " << e << " " << r << endl;
int theMax = max(q,w);
theMax = max(e,theMax);
theMax = max(r,theMax);
int need = 4*theMax-q-w-e-r;
int now = R-L+1;
int free = now-need;
// cout << now << ", " << need << endl;
if (free >= 0 && free%4 == 0) return true;
return false;
}
int main() {
scanf("%s", s);
int len = strlen(s);
q = w = e = r = 0;
for (int i = 0; i < len; i++) {
if (s[i] == 'Q') q++;
else if (s[i] == 'W') w++;
else if (s[i] == 'E') e++;
else if (s[i] == 'R') r++;
}
int ans = len;
if (check(0,-1)) {
ans = 0;
cout << ans << endl;
return 0;
}
if (s[0] == 'Q') q--;
else if (s[0] == 'W') w--;
else if (s[0] == 'E') e--;
else if (s[0] == 'R') r--;
int L = 0, R = 0;
while (R < len) {
while(L <= R && check(L,R)) {
ans = min(ans, R-L+1);
L++;
if (s[L-1] == 'Q') q++;
else if (s[L-1] == 'W') w++;
else if (s[L-1] == 'E') e++;
else if (s[L-1] == 'R') r++;
// cout << "lll " << L << "," << R << endl;
}
while (R < len && !check(L,R)) {
R ++;
if (s[R] == 'Q') q--;
else if (s[R] == 'W') w--;
else if (s[R] == 'E') e--;
else if (s[R] == 'R') r--;
// cout << "rrr " << L << "," << R << endl;
}
if(R >= len) break;
}
cout << ans << endl;
return 0;
}
D - 滑动窗口 POJ - 2823
题目描述
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 Input
-1 -3 -3 -3 3 3
3 3 5 5 6 7
题解
- 由于最小值和最大值是同一性质的问题,问题可以变为:如果在滑动窗口的过程中维护最值。本题需求:将下一个要移动到的值与现有的值进行比较;将已经滑出的值删除。于是考虑单调栈,既可以维护前端的最值,又可以对后端已经不在窗口范围内的值删除,从而获取了当前区间的最值。
- 实现:用的stl deque,感觉手动模拟对于数组特殊数据可能会有很大的浪费,复杂的实现又没必要了。单调队列:队列中存储的是元素位置,每次往右移动1,然后计算出此时左端点应该在哪,队列中值在左端点之前的直接删掉;然后处理右侧,将下一个值与队列中已有的进行比较,如果我们要的是最小值,那么就把大于的都删掉,如果要的是最大值,那么把小于的都弹出,最后把这个值推进去。每次移动后队尾的元素就是维护的最值。
代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <stack>
using namespace std;
const int MAXN = 1000000+10;
int n, k;
int a[MAXN];
int main() {
cin >> n >> k;
for(int i = 0; i < n; ++i)
scanf("%d", &a[i]);
deque<int> q;
int r = 0;
for(r = 0; r < k; ++r) {
while (q.size() && a[r] <= a[q.front()]) {
q.pop_front();
}
q.push_front(r);
}
r--;
cout << a[q.back()] << " ";
while (r+1 < n) {
r++;
int l = r-k+1;
if (l > q.back()) {
q.pop_back();
}
while (q.size() && a[r] <= a[q.front()]) {
q.pop_front();
}
q.push_front(r);
cout << a[q.back()] << " ";
}
cout << endl;
while (q.size()) q.pop_front();
for(r = 0; r < k; ++r) {
while (q.size() && a[r] >= a[q.front()]) {
q.pop_front();
}
q.push_front(r);
}
r--;
cout << a[q.back()] << " ";
while (r+1 < n) {
r++;
int l = r-k+1;
if (l > q.back()) {
q.pop_back();
}
while (q.size() && a[r] >= a[q.front()]) {
q.pop_front();
}
q.push_front(r);
cout << a[q.back()] << " ";
}
cout << endl;
return 0;
}