文章目录
一,最大矩形
描述
给一个直方图,求直方图中的最大矩形的面积。例如,下面这个图片中直方图的高度从左到右分别是2, 1, 4, 5, 1, 3, 3, 他们的宽都是1,其中最大的矩形是阴影部分。
Input
输入包含多组数据。每组数据用一个整数n来表示直方图中小矩形的个数,你可以假定1 <= n <= 100000. 然后接下来n个整数h1, …, hn, 满足 0 <= hi <= 1000000000. 这些数字表示直方图中从左到右每个小矩形的高度,每个小矩形的宽度为1。 测试数据以0结尾。
Output
对于每组测试数据输出一行一个整数表示答案。
Example
输入
7 2 1 4 5 1 3 3
4 1000 1000 1000 1000
0
输出
8
4000
思路
这道题需要开long long,否则数据会超范围。用结构体frame存储初始矩形的标号和高度。对于题中的每一个小矩形,如果其左右两边的矩形的高度全都不小于它自身的高度,则可向两边延伸。我们利用单调递减栈分别记录每一个小矩形左边第一个高度比其自身高度小的,右边同理。往右寻找代码如下:
s.push(h[0]);
for(int i=1; i<n; i++){//往右边找第一个小的,单调递减栈
frame top=s.top();
while(h[i].height < top.height){
rs[top.index]=i;
s.pop();
if(s.empty())break;
top=s.top();
}
s.push(h[i]);
}
每个小矩形左右记录好第一个比自己小的矩形过后,遍历输出面积最大值;
计算标号为i的小矩形左右延伸的最大矩形代码如下:
long long Squre(long i){
if(ls[i] != -1 && rs[i] != -1)
return h[i].height*(rs[i]-ls[i]-1);
if(ls[i]==-1 && rs[i]!=-1)
return h[i].height*rs[i];
if(ls[i] != -1 && rs[i] == -1)
return h[i].height*(n-ls[i]-1);
if(ls[i]==-1 && rs[i]==-1)
return h[i].height*n;
}
代码
#include<iostream>
#include<stdio.h>
#include<algorithm>
#include<stack>
using namespace std;
const int maxn = 1e5+10;
struct frame{
long long height;
long long index;
frame(){};
frame(long long a,long long b){
height=a;
index=b;
}
};
frame h[maxn];
long ls[maxn],rs[maxn];
long n,squre;
stack<frame> s; //维护dandiao递减栈;
long long Squre(long i){
if(ls[i] != -1 && rs[i] != -1)
return h[i].height*(rs[i]-ls[i]-1);
if(ls[i]==-1 && rs[i]!=-1)
return h[i].height*rs[i];
if(ls[i] != -1 && rs[i] == -1)
return h[i].height*(n-ls[i]-1);
if(ls[i]==-1 && rs[i]==-1)
return h[i].height*n;
}
int main()
{
while(scanf("%d",&n))
{ if(n==0)break;
for(int i=0; i<n; i++){
scanf("%d",&h[i].height);
h[i].index=i;
}
for(int i=0; i<n; i++){
ls[i]=-1;
rs[i]=-1;
}
s.push(h[0]);
for(int i=1; i<n; i++)
{//往右边找第一个小的,单调递减栈
frame top=s.top();
//cout<<top.index<<' '<<top.height<<endl;
while(h[i].height < top.height)
{
rs[top.index]=i;
s.pop();
if(s.empty())break;
top=s.top();
}
s.push(h[i]);
}
while(!s.empty()) s.pop();
s.push(h[n-1]);
for(int i=n-2; i>=0; i--){//往右边找第一个小的
frame top=s.top();
while(h[i].height<top.height && s.size()>0){
ls[top.index]=i;
s.pop();
if(s.empty())break;
top=s.top();
}
s.push(h[i]);
}
while(!s.empty()) s.pop();
//计算最大面积;
long long ans= Squre(0);
for(int i=1; i<n; i++){
long long sq=Squre(i);
if(sq>ans) ans=sq;
}
cout<<ans<<endl;
}
return 0;
}
二,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 cities from the world map, and represents the asset value owned by the -th city.Then the magic cat will perform several operations. Each turn is to choose the city in the interval and increase their asset value by . And finally, it is required to give the asset value of each city after operations.Could you help TT find the answer?
简要概括就是给定一个数组,有若干次操作,每次选定一个区间给区间内的数加上或减去相同的数,最后在输出数组中的元素。
Input
The first line contains two integers — the number of cities and operations.
The second line contains elements of the sequence : integer numbers Then lines follow, each line represents an operation. The -th line contains three integers and for the -th operation.
Output
Print integers one per line, and should be equal to the final asset value of the -th city.
Example
第一组
4 2
-3 6 8 4
4 4 -2
3 3 1
-3 6 9 2
第二组:
2 1
5 -2
1 2 4
9 2
第三组
1 2
0
1 1 -8
1 1 -6
-14
思路
简单的想法是每次操作遍历区间内每一个数进行操作,加入有m次操作,平均区间长度为n,则复杂度为O(mn)。
我们利用差分,在输入原数组 a[i] 的时候用数组 b[i] 记录每一个元素相对于上一个元素的变化(b[i]=a[i]-a[i-1]),有点类似于给你一个起点,告诉你源点和后续每一步的步长,那么一次遍历就可以得到所有走过的状态。
这里对每一次区间[l,r]操作,我们注意到对于区间内的元素,我们可以使b[l]加上对应的value,那么在最后迭代的时候l往后的元素均变化value,为了消除对r之后的元素的影响,我们还要使b[r+1]减去value。
代码
#include<iostream>
#include<stdio.h>
#include<algorithm>
#include<stack>
using namespace std;
const int maxn=1e6+10;
int n,q,l,r,c;
long long ans;
int a[maxn];
int b[maxn];//记录原数组的差分;
int main()
{
scanf("%d%d",&n,&q);
for(int i=1; i<=n; i++)
{
scanf("%d",&a[i]);
if(i==1)b[1]=a[1];
else b[i]=a[i]-a[i-1];//差分;
}
while(q--){
scanf("%d%d%d",&l,&r,&c);
b[l]+=c;
b[r+1]-=c;
}
for(int i=1; i<=n; i++){
ans+=b[i];
cout<<ans<<' ';
}
return 0;
}
三,平衡字符串
描述
一个长度为 n 的字符串 s,其中仅包含 ‘Q’, ‘W’, ‘E’, ‘R’ 四种字符。 如果四种字符在字符串中出现次数均为 n/4,则其为一个平衡字符串。现可以将 s 中连续的一段子串替换成相同长度的只包含那四个字符的任意字符串,使其变为一个平衡字符串,问替换子串的最小长度?如果 s 已经平衡则输出0。
Input
一行字符表示给定的字符串s
Output
一个整数表示答案
Example
第一组,
QWER
0
第二组,
QQWE
1
第三组,
QQQW
2
第四组,
QQQQ
3
思路
我们用q[maxn],w[maxn],e[maxn],r[maxn] 四个数组来记录四个数组的前缀和。先输入字符串,之后遍历每一个字符记录四个字母的前缀和。如果四个字母 q[n]==w[n]&&w[n]==e[n]&&e[n]==r[n],即个数一致,则字符串已经平衡。
从左到右尺取每一个可能的答案区间 [l,r] 。若 [l,r] 内修改后字符串平衡,考虑到r++对答案没有贡献,l++对答案可能又贡献,我们令l++再次判断区间,每次判断成功比较区间长度最小值。
对于判断区间[l,r]是否可成功修改使字符串平衡,我们记录每个字符在[l,r]之外的数量:
qn=q[n]-q[R]+q[L-1];
wn=w[n]-w[R]+w[L-1];
en=e[n]-e[R]+e[L-1];
rn=r[n]-r[R]+r[L-1];
maxlr=max(max(qn,wn),max(en,rn)) 记录在区间外数量最多的字符。那么如果区间[l,r]能够弥补非最多字符的个数,那么区间[l,r]便为成功区间。
进一步判断如下:
int lrt=R-L+1;//区间长度
lrt -= maxlr*4-qn-wn-en-rn;//弥补后剩余长度;
if(lrt>=0 && lrt%4==0)//注意mode4;
{//满足条件
ans=min(ans,R-L+1);
L++;
}
代码
#include<iostream>
#include<stdio.h>
#include<algorithm>
#include<stack>
#include<string>
#include<string.h>
using namespace std;
const int maxn=1e5+10;
char c[maxn];
long n,ans;
string ch;
int q[maxn],w[maxn],e[maxn],r[maxn];//前缀和;
int main()
{
while(cin>>ch)
{
n=ch.length();
for(int i=1; i<=n; i++)
{
if(ch[i-1]=='Q'){
q[i]=q[i-1]+1;
w[i]=w[i-1];
e[i]=e[i-1];
r[i]=r[i-1];
continue;
}
if(ch[i-1]=='W'){
q[i]=q[i-1];
w[i]=w[i-1]+1;
e[i]=e[i-1];
r[i]=r[i-1];
continue;
}
if(ch[i-1]=='E'){
q[i]=q[i-1];
w[i]=w[i-1];
e[i]=e[i-1]+1;
r[i]=r[i-1];
continue;
}
if(ch[i-1]=='R'){
q[i]=q[i-1];
w[i]=w[i-1];
e[i]=e[i-1];
r[i]=r[i-1]+1;
continue;
}
}
if(q[n]==w[n]&&w[n]==e[n]&&e[n]==r[n])
cout<<0<<endl;
else
{
long L=1,R=1,ans=n+1;
int qn,wn,en,rn;
while(L<=R && R<=n){
//在[l,r]之外的数量;
qn=q[n]-q[R]+q[L-1];
wn=w[n]-w[R]+w[L-1];
en=e[n]-e[R]+e[L-1];
rn=r[n]-r[R]+r[L-1];
int maxlr=max(max(qn,wn),max(en,rn));
int lrt=R-L+1;
lrt -= maxlr*4-qn-wn-en-rn;
if(lrt>=0 && lrt%4==0)
{//满足条件
ans=min(ans,R-L+1);
L++;
}
else{
R++;
}
}
cout<<ans<<endl;
}
}
return 0;
}
四,滑动窗口滑动窗口
描述
ZJM 有一个长度为 n 的数列和一个大小为 k 的窗口, 窗口可以在数列上来回移动. 现在 ZJM 想知道在窗口从左往右滑的时候,每次窗口内数的最大值和最小值分别是多少. 例如:数列是 [1 3 -1 -3 5 3 6 7], 其中 k 等于 3.
Input
输入有两行。第一行两个整数n和k分别表示数列的长度和滑动窗口的大小,1<=k<=n<=1000000。第二行有n个整数表示ZJM的数列。
Output
输出有两行。第一行输出滑动窗口在从左到右的每个位置时,滑动窗口中的最小值。第二行是最大值。
Example
输入
8 3
1 3 -1 -3 5 3 6 7
输出
-1 -3 -3 -3 3 3
3 3 5 5 6 7
思路
我们维护一个数组来模拟单调队列。l 和 r用来记录队列的头和尾。
对于寻找区间内最小值,我们循环时判断一下队列的首元素是不是区间内元素,如果不是,我们要把其去掉。对于新加入的元素,判断是不是比队尾元素小,为了保持队列有序,需要r- -,并加入新元素,cout<<num[index[l]] 为一次输出答案。最小值代码如下,最大值类似。
int l = 0,r = -1;
for(int i=0; i<n; i++)
{//区间最小值;
if(l <=r && index[l] < i-k+1) l++; //无效元素出队
while(l<=r && num[index[r]] >= num[i])
r--;//保持队列递增
index[++r] = i;//入队
if(i>=k-1)
cout<<num[index[l]]<<' ';//窗口到K
}
代码
#include<iostream>
#include<stdio.h>
#include<algorithm>
#include<stack>
#include<deque>//双向队列;
#include<string>
#include<string.h>
using namespace std;
/*
数组模拟队列;l r 为模拟队列左右边界;
*/
const int maxn = 1e6+10;
int n,k;
int num[maxn],index[maxn];
int main()
{
cin>>n>>k;
for(int i=0; i<n; i++)
scanf("%d",&num[i]);
//最小值
int l = 0,r = -1;
for(int i=0; i<n; i++)
{
if(l <=r && index[l] < i-k+1) l++; //无效元素出队
while(l<=r && num[index[r]] >= num[i])
r--;//保持队列递增
index[++r] = i;//入队
if(i>=k-1)
cout<<num[index[l]]<<' ';//窗口到K
}
cout<<endl;
//最大值
l = 0;
r = -1;
for(int i=0; i<n; i++)
{
if(l <=r && index[l] < i-k+1) l++;
while(l<=r && num[index[r]] <= num[i])
r--;//保持队列递减
index[++r] = i;
if(i>=k-1)
cout<<num[index[l]]<<' ';
}
return 0;
}
五,总结
1,要对题中的数据范围有大概的估计,按最坏情况考虑,否则会出现A题的错误。
2,差分可以在线性复杂度下完成对一维数组的多次区间变化。
3,单调栈和单调队列对于求左边第一个不大于、小于等搜索问题可以提供思路。