A - 最大矩形
题意
给一个直方图,求直方图中的最大矩形的面积。例如,下面这个图片中直方图的高度从左到右分别是2, 1, 4, 5, 1, 3, 3, 他们的宽都是1,其中最大的矩形是阴影部分。
Input
输入包含多组数据。每组数据用一个整数n来表示直方图中小矩形的个数,你可以假定1 <= n <= 100000. 然后接下来n个整数h1, …, hn, 满足 0 <= hi <= 1000000000. 这些数字表示直方图中从左到右每个小矩形的高度,每个小矩形的宽度为1。 测试数据以0结尾。
Output
对于每组测试数据输出一行一个整数表示答案。
思路
对于所有小矩形,向左向右延伸到最长的时候那个矩形的面积中最大值就是最大矩形面积,但是如果直接暴力的话复杂度是 O(n^2),肯定会超时,我们可以用单调栈来把复杂度降到O(n)。
对于每个矩形最右边界,我们维护一个递增栈,第一个矩形入栈,从第二个矩形到最后依次入栈并判断,如果入栈的矩形高度大于等于栈顶高度的话,直接入栈,否则将栈顶弹出,很明显,这个弹出的元素最右边界是当前要入栈的元素下表减1,将结果记录到数组r[MAX]中,直到当前元素入栈后开始取下一个矩形。要注意的是,最后一个元素入栈之后可能递增栈不为空,所以里面的元素最右边界都是最后一个元素的标号。
最左边界就是反过来再进行一次上述操作,最后求最大面积即可。
总结
这道题是递增栈的应用,但要注意数据的范围是long long。
代码
#include<iostream>
#include<stdio.h>
#include<stack>
using namespace std;
int l[100000+5],r[100000+5],a[100000+5];
void right(int *a,int n)
{
stack<int> s;
for(int i=0;i<n;i++)
{
while(!s.empty()&&a[i]<a[s.top()])
{
r[s.top()]=i-1;
s.pop();
}
s.push(i);
}
int rr=-1;
if(!s.empty())
rr=s.top();
while(!s.empty())
{
r[s.top()]=rr;
s.pop();
}
}
void left(int *a,int n)
{
stack<int> s;
for(int i=n-1;i>=0;i--)
{
while(!s.empty()&&a[i]<a[s.top()])
{
l[s.top()]=i+1;
s.pop();
}
s.push(i);
}
int ll=-1;
if(!s.empty())
ll=s.top();
while(!s.empty())
{
l[s.top()]=ll;
s.pop();
}
}
int main()
{
int n;
scanf("%d",&n);
while(n!=0)
{
for(int i=0;i<n;i++)
scanf("%d",&a[i]);
right(a,n);
left(a,n);
long long maxs=-1;
for(int i=0;i<n;i++)
{
long long temp=(long long)(r[i]-l[i]+1)* (long long)a[i];
if(temp > maxs)
maxs=temp;
}
cout<<maxs<<endl;
scanf("%d",&n);
}
}
B - 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 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
The first line contains two integers n,q (1≤n,q≤2⋅105) — the number of cities and operations.
The second line contains elements of the sequence a: integer numbers a1,a2,…,an (−106≤ai≤106).
Then q lines follow, each line represents an operation. The i-th line contains three integers l,r and c (1≤l≤r≤n,−105≤c≤105) for the i-th operation.
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.
思路
这道题直接暴力显然会超时,所以我们可以用前缀和来做
对于输入的数组a[MAX],我们构造一个新的数组b,使得 b[0] = a[0],b[i] = a[i] - a[i-1] (i>1)
这样一来,我们求 a[i] 的值就可以转化为求数组b前 i 项的和,所以我们如果要对 [i, j] 区间内所有的a数组的值加上c的话就可以简单地转化为 b[i] = b[i] + c,b[ j+1] = b[ j+1] - c;只需要两步操作即可
最后按b数组输出 a[i] 的值
总结
这道题开拓了自己的思路,用前缀和可以快速地完成区间上的加减。
代码
#include<iostream>
#include<stdio.h>
using namespace std;
long long a[200000+5],b[200000+5];
int main()
{
int n,q;
cin>>n>>q;
for(int i=0;i<n;i++)
scanf("%lld",&a[i]);
b[0]=a[0];
for(int i=1;i<n;i++)
b[i]=a[i]-a[i-1];
while(q--)
{
int l,r,c;
scanf("%d%d%d",&l,&r,&c);
b[l-1]+=c;
if(r-1<n-1)
b[r]-=c;
}
long long sum=0;
for(int i=0;i<n;i++)
{
sum+=b[i];
if(i!=n-1)
printf("%lld ",sum);
else
printf("%lld\n",sum);
}
}
C - 平衡字符串
题意
一个长度为 n 的字符串 s,其中仅包含 ‘Q’, ‘W’, ‘E’, ‘R’ 四种字符。
如果四种字符在字符串中出现次数均为 n/4,则其为一个平衡字符串。
现可以将 s 中连续的一段子串替换成相同长度的只包含那四个字符的任意字符串,使其变为一个平衡字符串,问替换子串的最小长度?
如果 s 已经平衡则输出0。
Input
一行字符表示给定的字符串s
Output
一个整数表示答案
思路
我们先统计输入字符串中四个字符出现的次数,如果已经平衡了的话直接输出0
若没有,用尺取的思想维护两个变量 l、r,代表我们要更改的连续字符串左右端点,由第一个字符开始加入区间,r++,然后区间外的对应字符次数减1,然后找出区间外部四个字符次数最大的,用区间内部的字符补齐那些小的,(1)如果补齐之后区间内部剩余字符数大于等于零,并且模4等于零的话,意味着可以平衡,然后更新一下最小区间长度的值(如果 r-l+1 更小的话),之后如果r++,对我们的答案是可能有贡献的,因为可能是区间长度更小,l++是没有贡献的,只要不变就行,所以我们选择r++,继续循环。(2)如果补齐之后不符合条件,那么我们r++更不可能符合条件,所以我们选择l++,继续循环。之后输出最小长度的值。
总结
这道题难点在于如何判断改变一段连续的字符串后是否能够平衡,这里是根据用区间内的字符去补齐那些少的字符数,看剩余的区间内字符串是否能够符合条件。
代码
#include<iostream>
#include<stdio.h>
#include<map>
#include<string>
using namespace std;
int a[4];
int main()
{
map<char,int> m;
m.insert({'Q',0});
m.insert({'W',1});
m.insert({'E',2});
m.insert({'R',3});
for(int i=0;i<3;i++)
a[i]=0;
string str;
cin>>str;
for(int i=0;i<str.length();i++)
a[m[str[i]]]++;
int len=str.length();
if(a[0]==len/4&&a[1]==len/4&&a[2]==len/4&&a[3]==len/4)
{
cout<<0<<endl;
return 0;
}
int l=0,r=0;
int minl=str.length();
a[m[str[0]]]--;
while(r<len&&l<=r)
{
int maxc=max(max(a[0],a[1]),max(a[2],a[3]));
int size=r-l+1;
size-= maxc-a[0] +maxc-a[1]+maxc-a[2]+maxc-a[3];
if(size>=0&&size%4==0)
{
if(r-l+1<minl)
minl=r-l+1;
a[m[str[l]]]++;
l++;
}
else
{
r++;
a[m[str[r]]]--;
}
}
cout<<minl<<endl;
}
D - 滑动窗口滑动窗口
题意
ZJM 有一个长度为 n 的数列和一个大小为 k 的窗口, 窗口可以在数列上来回移动. 现在 ZJM 想知道在窗口从左往右滑的时候,每次窗口内数的最大值和最小值分别是多少. 例如:
数列是 [1 3 -1 -3 5 3 6 7], 其中 k 等于 3.
思路
这道题要求窗口滑动的时候的最小值和最大值,我们可以用一个双向递增队列和一个双向递减队列来分别求取,我们要求的最小值和最大值就是这两个队列的队首元素。
用两个数组来存放结果,两个num结构体数组来表示我们维护的两个队列,对于递增队列,入队时与队尾元素的值比较,如果大于等于队尾元素直接入队,否则删除队尾元素,知道队列为空或者由满足递增后入队,之后判断与队首元素下表的差值+1是否大于了窗口长度,如果大于则队首元素出队,之后队列的队首元素就是窗口当前位置的最小值了。
对于递增队列,其方法与上述相似,最后将我们记录下的最大值、最小值按题目要求输出即可。
总结
一开始使用的stl的deque,提交之后超时了,最后改成了自己用数组模拟的队列才ac
代码
#include<iostream>
#include<stdio.h>
#include<deque>
using namespace std;
int a[1000000+5];
struct num
{
int value,index;
num(int value1,int index1)
{
value=value1;
index=index1;
}
num()
{
}
};
num min1[1000005],max1[1000005];
int min2[1000005],max2[1000005];
int main()
{
deque<num> q1,q2;
int n,k;
scanf("%d%d",&n,&k);
for(int i=0;i<n;i++)
scanf("%d",&a[i]);
int minl,maxl,minr,maxr;
minl=maxl=0;
minr=maxr=-1;
for(int i=0;i<n;i++)
{
while(minl<=minr&&min1[minr].value>a[i])
minr--;
if(minl<=minr&&i-min1[minl].index>k-1)
minl++;
min1[++minr]=num(a[i],i);
min2[i]=min1[minl].value;
while(maxl<=maxr&&max1[maxr].value<a[i])
maxr--;
if(maxl<=maxr&&i-max1[maxl].index>k-1)
maxl++;
max1[++maxr]=num(a[i],i);
max2[i]=max1[maxl].value;
}
for(int i=k-1;i<n;i++)
printf(i!=n-1?"%d ":"%d\n",min2[i]);
for(int i=k-1;i<n;i++)
printf(i!=n-1?"%d ":"%d\n",max2[i]);
}