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
思路
利用单调非递减栈来完成该题。
这里采用的单调非递减栈是从栈顶到栈底的非单调递减。
即:栈顶------非单调递减------->栈底。
考虑因为要求最大的矩形面积,即需要确定每一个矩形能够向左向右最远扩展多远,用这个最大的范围乘矩形的高度,即每个矩形可以扩充的最大面积。
因此能够找到每一个矩形的左边和右边第一个比它小的矩形的位置很重要。
拿样例7 2 1 4 5 1 3 3来说:
初始:
h | 2 | 1 | 4 | 5 | 1 | 3 | 3 |
---|---|---|---|---|---|---|---|
i | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
R | |||||||
L | |||||||
area |
正序遍历,依次放入栈中:
栈------非单调-----栈
顶--------递减------尾
[2]//栈空放入2
[1]//因为栈顶元素2>1,若放入1,则变成[1,2],不满足条件,弹出2
由于2被1弹出,说明1比2小,因为是正序遍历,因此1是2向右第一个比2小的元素。
因此:2的R=1的i-1;
h | 2 | 1 | 4 | 5 | 1 | 3 | 3 |
---|---|---|---|---|---|---|---|
i | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
R | 0 | ||||||
L | |||||||
area |
[4,1]//接着放入4
[5,4,1]//放入5
[1,1]//放入1,将5,4弹出,因此5的R=此次放入的1的i-1,4的R同理
h | 2 | 1 | 4 | 5 | 1 | 3 | 3 |
---|---|---|---|---|---|---|---|
i | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
R | 0 | 3 | 3 | ||||
L | |||||||
area |
[3,1,1]//放入3
[3,3,1,1]//放入3
一次遍历结束,但是栈里还有元素,那么这些元素的向右能够扩展的最远的位置就是n-1
h | 2 | 1 | 4 | 5 | 1 | 3 | 3 |
---|---|---|---|---|---|---|---|
i | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
R | 0 | 6 | 3 | 3 | 6 | 6 | 6 |
L | |||||||
area |
此时得到了每个元素的R,倒序遍历一遍即可得到每个元素的L,为了节省空间,可以不记录每个元素L,得到L的时候直接进行面积计算。我们可以规定一个元素的L=向左边第一个比它小的元素的i+1,因此其面积就可以用h*(R-L+1)来计算得到。
输出最大的面积即可。
反思
在该题中,我WA了三次,均是在第17组出现WA,经过同学提点,当矩形的h和(R-L+1)相乘的结果比较大的时候,用int来表示area可能会因为数据太大而溢出,因此将area的类型设定为long long即可通过。
代码
#include<iostream>
#include<algorithm>
#include<stack>
using namespace std;
struct LR
{
int h;
int i;//位置标号
int R;//右边第一个比它小的元素的位置
long long area;//面积
LR()
{
h = i = R = 0;
area = 0;
}
};
bool cmp(const LR& a, const LR& b)
{
return a.area > b.area;
}
LR len[100010];
int n;
int main()
{
while (1)
{
cin >> n;
if (n == 0)//结束
{
break;
}
for (int i = 0; i < n; i++)
{
cin >> len[i].h;
len[i].i = i;
}
stack<LR> st;
//找到每个矩形往右第一个比它小的矩形的位置
//st.push(len[0]);
for (int i = 0; i < n; i++)
{
//如果栈顶元素比当前要放入的元素大
//即放入当前元素后不满足从栈顶到栈底的非单调递减,弹出栈顶元素
//此时栈顶元素的向右第一个比它小的元素即为要放入的该元素,记录此位置
while (st.size() > 0 && st.top().h > len[i].h)
{
len[st.top().i].R = i - 1;
st.pop();
}
st.push(len[i]);
}
//如果栈内还有元素,则这些元素向右最远的位置为末端位置
while (st.size() != 0)
{
len[st.top().i].R = n - 1;
st.pop();
}
//找到每个矩形往左第一个比它小的矩形的位置
//st.push(len[n - 1]);
for (int i = n - 1; i >= 0; i--)
{
//如果栈顶元素比当前要放入的元素大
//即放入当前元素后不满足从栈顶到栈底的非单调递减,弹出栈顶元素
//此时栈顶元素的向左第一个比它小的元素即为要放入的该元素,记录此位置
while (st.size() > 0 && st.top().h > len[i].h)
{
//st.top().L = i + 1;
//st.top().area = st.top().h * (st.top().R - st.top().L + 1);//面积计算公式
len[st.top().i].area = (long long)(st.top().h) * (long long)(st.top().R - i);//计算面积
st.pop();
}
st.push(len[i]);
}
//如果栈内还有元素,则这些元素向左最远的位置为首端位置
while (st.size() != 0)
{
//st.top().L = 0;
len[st.top().i].area = (long long)(st.top().h) * (long long)(st.top().R + 1);//计算面积
st.pop();
}
sort(len, len + n, cmp);//按面积大小升序排序
cout << len[0].area << endl;
}
}
B-TT’s Magic Cat
上星期多亏了大家的帮助,TT终于有了一只可爱的猫。但没想到的是这是一只神奇的猫。
有一天,这只神奇的猫决定给TT一个问题来研究它的能力。即从世界地图中选择n个城市,a[i]表示第i个城市拥有的资产价值。
然后魔术猫将执行几个操作。每轮选择区间[l,r]中的城市,将其资产价值增加c,最后给出q运算后各城市的资产价值。
你能帮TT找到答案吗?
Input
第一行包含两个整数n,q(1≤n,q≤2⋅105)-城市和运营的数量。
第二行包含序列a的元素:整数a1、a2、…、an(-106≤ai≤106)。
接下来是q行,每一行代表一个操作。第i行包含用于第i操作的三个整数l、r和c(1≤l≤r≤n,-105≤c≤105)。
Output
打印n个整数a1,a2,…,每行一个,ai应等于第i个城市的最终资产值。
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
思路
用数组A来存储每个城市的价值,因为考虑到每次都是在[l,r]连续区间内,对A数组里的数值进行+c的运算,如果每次都遍历[l,r]无疑是比较耗时的方法。
因此考虑采用差分数组和前缀和来进行计算。
用B数组来记录A数组的差分。
即B[0]=A[0],B[i]=A[i]-A[i-1]
每次在[l,r]区间+c。
因为B[l]=A[l]-A[l-1],A[l]+c了,而A[l-1]保持不变,因此B[l]+c,
同理可以得到在B数组中,B[r+1]需要-c,而其他的均不需要进行改变。
可以大大降低时间复杂度。
因为前缀和的计算公式为:
sum[0]=B[0],sum[i]=sum[i-1]+B[i]
因此可以知道B数组的前缀和即为全部更改后的A数组。
为了节省空间,也可以将B数组的前缀和数组覆盖在B数组上。
这道题WA了两次,将A,B数组都改成long long型之后就过了,考虑数据的大小。
代码
#include<iostream>
#include<algorithm>
#include<stack>
using namespace std;
/*B-TT's Magic Cat*/
int n, q;
long long A[200010];
long long B[200010];//B是A的差分数组
int main()
{
cin >> n >> q;
cin >> A[0];
B[0] = A[0];
for (int i = 1; i < n; i++)
{
cin >> A[i];
B[i] = A[i] - A[i - 1];//完成差分数组
}
for (int i = 0; i < q; i++)
{
int l, r;
long long c;
cin >> l >> r >> c;
B[l-1] += c;
if (r < n)
{
B[r] -= c;
}
}
//计算B数组的前缀和
for (int i = 1; i < n; i++)
{
B[i] = B[i - 1] + B[i];//将B的前缀和放入B数组中,节省空间
}
for (int i = 0; i < n; i++)
{
cout << B[i] << " ";
}
}
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<=10^5
n是4的倍数
字符串中仅包含字符 ‘Q’, ‘W’, ‘E’ 和 ‘R’.
思路
该题可以采取尺取法。
首先统计字符串中四个字符的个数。
如果刚好sumQ=sumW=sumE=sumR=n/4,说明字符串本身就是平衡字符串,需要替换的字符数为0,直接输出结果。
如果字符串本身不为平衡字符串,则:
从字符串的首端开始向后移动,取[l,r]区间,判断条件。
如果更改[l,r]区间内部字符可以满足使字符串成为平衡字符串,则l++;不选取r++是因为如果r++,则区间变成[l,r+1],由于[l,r]区间已经满足条件,[l,r+1]区间只要r+1不进行更改一定也满足条件,对结果没有贡献。
如果更改[l,r]区间内不的字符不能使字符串变成平衡字符串,则r++;
具体过程已经清楚,那么什么时候满足条件呢。
我们可以假定sumQ,sumW,sumE,sumR统计的是除去[l,r]区间内的字符外剩下的字符串中Q,W,E,R的个数。
考虑,如果进行l++,那么原来l位置就变成了[l,r]区间以外的位置,因此相应的sum统计应该进行改变,此时对应的sum应该+1;
同理,如果进行r++,那么r++之后r位置就变成了[l,r]区间以内的位置,因此相应的sum统计应该进行改变,此时对应的sum应该-1;
用Max来记录sumQ,sumW,sumE,sumR中最大的值
在[l,r]区间内可以更改的位置个数为total=r-l+1;
进行填平操作并记录空闲的位置:
free=total-((Max-sumQ)+(Max-sumW)+(Max-sumE)+(Max-sumR));
如果空闲位置大于等于0,证明是可以进行填平操作的,并且如果free%4==0说明符合条件。此时可以进行l++;
并且此时更改的字符个数为r-l+1;
分析完成后就可以进行尺取法啦!
代码
#include<iostream>
#include<algorithm>
#include<string>
#include<cmath>
#include<queue>
#include<stdio.h>
using namespace std;
char str[1000010];
int n = 1;
int sumQ = 0, sumW = 0, sumE = 0, sumR = 0;
void rule()
{
int ans = n + 1, L = 1, R = 0;
while (R < n)
{
int Max = max(max(sumE, sumQ), max(sumR, sumW));
int total = R - L + 1;
int free = total - ((Max - sumE) + (Max - sumQ) + (Max - sumR) + (Max - sumW));
while (L <= R && free >= 0 && free % 4 == 0)
{
ans = min(ans, R - L + 1);
//重新计算sum
if (L != R)
{
if (str[L] == 'E')
{
sumE++;
}
else if (str[L] == 'Q')
{
sumQ++;
}
else if (str[L] == 'R')
{
sumR++;
}
else if (str[L] == 'W')
{
sumW++;
}
}
L++;
Max = max(max(sumE, sumQ), max(sumR, sumW));
total = R - L + 1;
free = total - ((Max - sumE) + (Max - sumQ) + (Max - sumR) + (Max - sumW));
}
R++;
if (str[R] == 'E')
{
sumE--;
}
else if (str[R] == 'Q')
{
sumQ--;
}
else if (str[R] == 'R')
{
sumR--;
}
else if (str[R] == 'W')
{
sumW--;
}
}
cout << ans << endl;
}
int main()
{
while (scanf_s("%c", &str[n], 1) == 1 && str[n] != '\n')
{
//先统计字符串中各个字符的字数
if (str[n] == 'W')
{
sumW++;
}
else if (str[n] == 'Q')
{
sumQ++;
}
else if (str[n] == 'E')
{
sumE++;
}
else if (str[n] == 'R')
{
sumR++;
}
n++;
}
//cout << n << endl;
n--;
if (sumE == (n/4) && sumQ == (n/4) && sumR == (n/4) && sumW == (n/4))
{
cout << 0 << endl;
}
else
{
rule();
}
}
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
思路
该题采用单调队列的方式。
用队首至队尾非单调递增的队列来找到每一个k窗口中最小的元素。
用队首至队尾非单调递减的队列来找到每一个k窗口中最大的元素。
以数列[1 3 -1 -3 5 3 6 7]来举例说明。k=3。
队--------非单调------->队
首---------递增--------->尾
[1]---------先添加k个元素,队列为空放入1
[1,3]------队尾元素1<3,放入3
[-1]-------队尾元素3>-1,弹出3后,队尾1>-1,弹出1,队列空放入-1。开始的3个元素已经放入完毕,因此队首的元素即为该k个元素中最小的。
[-3]-------队尾-1>-3,弹出-1,放入-3。此时的k窗口中最小元素为队首-3
[-3,5]----队尾-3<5,放入5。此时的k窗口中最小元素为队首-3
[-3,3]----队尾5>3,弹出5后,队尾-3<3,放入3,此时的k窗口中最小元素为队首-3
[3,6]-----队尾3<6,又因为-3至6的距离超过了k,此时-3应该在k窗口外面,弹出-3,此时k窗口中最小元素为队首3
[3,6,7]–队尾6<7,放入7,此时k窗口中最小元素为队首3
根据此过程总结,在寻找k窗口的最小元素时,先放入开始的k元素,每次队列里的队首元素即为k窗口中的最小元素。
但是需要注意队首元素和队尾元素之间的距离是否在k窗口内。
寻找最大元素同理,只是使用的队列为队首至队尾非单调递减的队列。
反思
这道题我使用G++过的时候超时,改用C++跑就AC了。(有点玄学,逃)
写博客的时候突然想到,其实再求最大元素的时候,我的代码可以进行更改,不必倒叙遍历放入队列,完全可以正着来,但是当时想的时候被单调栈影响,因此采用的倒序遍历,用数组记录再倒序输出。
代码
#include<iostream>
#include<algorithm>
#include<string>
#include<cmath>
#include<queue>
#include<stdio.h>
using namespace std;
/*D-*/
int n, k;
struct sequence
{
int se;
int i;
sequence& operator=(const sequence& rv)
{
se = rv.se;
i = rv.i;
return *this;
}
};
int qmax[1000010];
sequence q[1000010];//模拟队列
int qsize = 0;//队列的size
int front = 0, back = -1;//队列的头和尾
sequence a[1000010];
int main()
{
cin >> n >> k;
for (int i = 0; i < n; i++)
{
scanf("%d", &a[i].se);
a[i].i = i;
}
//先放初始的k个数
for (int i = 0; i < k; i++)//首先放入初始的k的数
{
while (qsize > 0 && (i - q[front].i) >= k)//判断两个数是否在同一个k窗口内
{
front++;
qsize--;
}
while (qsize > 0 && q[back].se > a[i].se)//使队列从队首至队尾递增
{
back--;
qsize--;
}
q[++back] = a[i];
qsize++;
}
printf("%d ", q[front].se);
for (int i = k; i < n; i++)
{
while (qsize > 0 && (i - q[front].i) >= k)//判断两个数是否在同一个k窗口内
{
front++;
qsize--;
}
while (qsize > 0 && q[back].se > a[i].se)//使队列从队首至队尾递增
{
back--;
qsize--;
}
back++;
q[back] = a[i];
qsize++;
printf("%d ", q[front].se);
}
int j = 0;//qmax数组下标
front = 0, back = -1;//队列初始化
qsize = 0;
for (int i = n - 1; i >= n - k; i--)
{
while (qsize > 0 && (q[front].i - i) >= k)//判断两个数是否在同一个k窗口内
{
front++;
qsize--;
}
while (qsize > 0 && q[back].se < a[i].se)//使队列从队首至队尾递减
{
back--;
qsize--;
}
back++;
q[back] = a[i];
qsize++;
}
qmax[j] = q[front].se;
j++;
for (int i = n - k - 1; i >= 0; i--)
{
while (qsize > 0 && (q[front].i - i) >= k)//判断两个数是否在同一个k窗口内
{
front++;
qsize--;
}
while (qsize > 0 && q[back].se < a[i].se)//使队列从队首至队尾递减
{
back--;
qsize--;
}
back++;
q[back] = a[i];
qsize++;
qmax[j] = q[front].se;//每放入一个数据,将得到一个新的k窗口,即会得到该新的k窗口的最大值
j++;
}
cout << endl;
//输出最大的值
for (int i = j-1; i >= 0; i--)
{
printf("%d ", qmax[i]);
}
cout << endl;
}