最大矩形
给一个直方图,求直方图中的最大矩形的面积。例如,下面这个图片中直方图的高度从左到右分别是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
分析:考虑矩形递增的情况,比如从左到右高度依次为1,2,3,3,4,5
那么若想知道以3为高的大矩形面积,只要看2到5(就是2到最后)的宽度。
实际不一定是递增,那么用一个递增栈来维护递增的矩形。
栈:底–>顶 递增 (这一行说明是为了下面的描述方便)
栈中元素有性质:对于元素x左边第一个是这个矩形左边第一个不大于它的矩形,右边是大于他的矩形且是连续的(因为遇到小于等于x的x会出栈),这样x高度的矩形的宽左边界是x-1,右边界是x出栈时候的栈顶。当x左边没有元素时要特殊处理。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<stack>
using namespace std;
typedef long long LL;
int n;
LL s[100005];
int a[100005];
int top;
LL mxs;
stack<int> q;
int sum;
int x;
int main()
{
while (scanf_s("%d", &n) != EOF)
{
if (n == 0) return 0;
mxs = 0;
top = 0;
for (int i = 0; i < n; i++)
scanf_s("%lld", &s[i]);
s[n] = 0;
for (int i = 0; i <= n; i++)
{
sum = 0;
while (top != 0 && s[i] <= s[a[top]])
{
sum = i - a[top];
x = a[top];
top--;
sum += (top == 0 ? x : x - a[top] - 1);
mxs = max(mxs, s[x] * (sum));
}
a[++top] = i;
}
printf("%lld\n", mxs);
}
return 0;
}
TT’s Magic Cat
给一些数,做一些加操作,输出结果数组ai。第一行输入n(区间数),q(操作数),接下来一行输入n个数,接下来q行输入操作,格式为左边界l右边界r加数c。输出为一行数,就是被操作后的结果数组。
(1≤n,q≤2⋅105) (−106≤ai≤106) (−105≤c≤105) (1≤l≤r≤n)
Examples
Input
4 2
-3 6 8 4
4 4 -2
3 3 1
Output
-3 6 9 2
Input
2 1
5 -2
1 2 4
Output
9 2
Input
1 2
0
1 1 -8
1 1 -6
Output
-14
分析: 考虑大力出奇迹,复杂度O(qn),最大为4*1010,400亿。
把数组映射为差分数组,bi=ai-ai-1,b1=a1,则对于一个数组a一个区间[l,r]的加操作,对应b中之影响了l-1和r+1,复杂度从O(n)降到了O(1)。
变回去:a’n=b1+b2+…+bn,复杂度为O(n)。
#include <cstdio>
int a[300000];
int b[300000];
int main()
{
int n, q;
scanf("%d%d", &n, &q);
a[0] = 0;
int j, k;
j = 0;
for (int i = 1; i <= n; i++)
{
scanf("%d", &b[i]);
if (i == 1)
a[i] = b[i];
else
a[i] = b[i] - b[i - 1];
}
int l, r, x;
while (q--)
{
scanf("%d%d%d", &l, &r, &x);
a[l] += x;
a[r + 1] -= x;
}
long long sum = 0;
for (size_t i = 1; i <= n; i++)
{
sum += a[i];
printf("%lld ", sum);
}
}
平衡字符串
一个长度为 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<=10^5
n是4的倍数
字符串中仅包含字符 ‘Q’, ‘W’, ‘E’ 和 ‘R’.
分析:从头开始找到一个可行区间[l,r],拓展右边界,r+1,左边界相应要减2,因为已经确定可行区间大小r-l+1,新确认区间比之更小才有意义。
确认区间是否可行的方法是:计算出除了区间剩下的字母个数分别是多少,找到MAX,算出MAX和其他字母的差,在区间内填补这些差,剩下的free位置若是4的倍数,则可行。
#include <cstdio>
#include <map>
#include <algorithm>
using namespace std;
char str[1000005];
map<char, int> number;
map<char, int> number2;
int l, r, minLength;//r是尾后元素
char m;
char find_max()
{
char x = 'Q';
if (number2['W'] > number2[x]) x = 'W';
if (number2['E'] > number2[x]) x = 'E';
if (number2['R'] > number2[x]) x = 'R';
return x;
}
bool judge()
{
int total = r - l;
m = find_max();
int differ = 0;
for (auto c : number2)
differ += (number2[m] - c.second);
int free = total - differ;
if (free >= 0 && free % 4 == 0) return true;
return false;
}
int main()
{
number['Q'] = number['W'] = number['E'] = number['R'] = 0;
int i = 0;
while (~scanf_s("%c", &str[i]))
{
if (str[i] == '\n')
{
break;
}
number[str[i++]]++;
}
number2 = number;
minLength = INT_MAX;
while (r <= i)
{
if (judge())
{
number2[str[l]]++;
l++;
if (r - l + 1 < minLength)
minLength = r - l + 1;
}
else
{
number2[str[r]]--;
r++;
}
}
printf("%d\n", minLength);
}
滑动窗口
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
分析:遍历数组,维护一个递增队列和一个递减队列,以递增队列为例,它是用来确认窗口内的最小元素的,队列保持递减性质,若新元素小于等于队尾,则出列,这样保证队首是最小的,因为没有元素能让他出列。另外要判断队首是否是窗口移动过程中被移除的那个。
#include <cstdio>
#include <queue>
using namespace std;
int a[1000005], maximum[1000005], minimum[1000005];
deque<int> maxn;
deque<int> minn;
int main()
{
int n, k;
scanf_s("%d%d", &n, &k);
for (size_t i = 0; i < n; i++)
{
scanf_s("%d", &a[i]);
while (!maxn.empty() && a[maxn.back()] < a[i]) maxn.pop_back();
while (!minn.empty() && a[minn.back()] > a[i]) minn.pop_back();
maxn.push_back(i);
minn.push_back(i);
if (i >= k - 1)
{
if (i - maxn.front() + 1 > k) maxn.pop_front();
if (i - minn.front() + 1 > k) minn.pop_front();
maximum[i - k + 1] = maxn.front();
minimum[i - k + 1] = minn.front();
}
}
for (size_t i = 0; i < n - k + 1; i++)
printf("%d ", a[minimum[i]]);
printf("\n");
for (size_t i = 0; i < n - k + 1; i++) printf("%d ", a[maximum[i]]);
}