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
解题思路
-
高度确定时,怎么得到面积的最大值?
即向左,向右延伸,直到遇到第一个小于当前高度的,停止; 得到的面积为最大的。
-
所以遍历所有矩形,每次把高度设定为当前矩形的高度h,向左、向右找第一个高度小于h的,停止。面积即为h乘以中间的矩形数。
-
找第一个高度小于h的矩形时可以使用单调栈
-
遍历所有矩形,只要高度大于等于h则弹出,直到栈顶元素小于h或栈空,记录当前下标(栈空则0,非空栈顶元素+1),当前元素入栈
完整代码
//利用单调栈,向左向右遍历所有点,直到第一个小于它的点
//利用了他的顺序插入
#include<iostream>
using namespace std;
long long Left[100100],Right[100100];
long long a[100100];
long long stack[100100];
long long n;
int leftsize()
{
long long l=-1;
for(int i=0;i<n;i++)
{
while(l>=0&&a[i]<=a[stack[l]])//新的数小于栈顶元素
l--;//弹出去 ,剩左边最后一个比它小的元素
if(l==-1)
Left[i]=0;//左边没有比它大的元素了
else
Left[i]=stack[l]+1;
stack[++l]=i;//入栈
}
}
int rightsize()
{
long long r=-1;
for(int i=n-1;i>=0;i--)
{
while(r>=0&&a[i]<=a[stack[r]])//新的数小于栈顶元素
r--;//弹出去
if(r==-1)
Right[i]=n-1;//7 2 1 4 5 1 3 3
else
Right[i]=stack[r]-1;
stack[++r]=i;//入栈
}
}
int main()
{ long long max;
while(cin>>n)
{
if(n==0)
break;
max=0;
for(int i=0;i<n;i++)
cin>>a[i];
leftsize();
rightsize();
for(int i=0;i<n;i++)
{
long long p=a[i]*(Right[i]-Left[i]+1);
if(max<p)
max=p;
}
cout<<max<<endl;
}
}
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.
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
解题思路
- 本题考察差分数组的使用,即 b[0]=a[0]; b[i]=a[i]-a[i-1];
- 整理可得a[i]=求和(b[i])(i:0~i)
- 对所有a[i]实行的加减操作(需n次),只需对b[0]、b[n-1]分别进行一次,最后按序输出即可。加减操作的时间复杂度由n降为了2。
完整代码
#include<cstdio>
using namespace std;
long long a[200100],b[200100];
int main()
{
int n,q;
long long t=0;
long long l,r,c;
scanf("%d %d",&n,&q);
for(int i=1;i<=n;i++)
scanf("%lld",&a[i]);
b[1]=a[1];
for(int i=2;i<=n;i++)
b[i]=a[i]-a[i-1];
for(int i=0;i<q;i++)
{ scanf("%lld %lld %lld",&l,&r,&c);
{ b[l]=b[l]+c;b[r+1]=b[r+1]-c;}
}
for(int i=1;i<=n;i++)
{ t=t+b[i];
printf("%lld ",t);}
}
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
解题思路
- 本题考察尺取法(双指针法)的使用
- 尺取法使用条件:
- 所求解答案为一个连续区间
- 区间左右端点移动有明确方向
- 本题可从左端点开始,向右遍历所有区间 如果当前区间不符合要求,则扩大区间(右端点右移),如果符合要求则缩小区间(左端点右移)。符合尺取法的使用条件
- 考虑:任取区间[L,R],如何判断是否符合要求?
- 先判断是否给定的字母已满足要求。统计所有字母个数看是否相等。
- 记录区间外四个字母的个数
- 通过拿[L,R]中的字母替换使四个字母个数相等,再判断[L,R]中剩余的自由字母是否大于0且为4的倍数,是则可以,不是则失败。
- 从0开始,每次判断[L,R]是否符合,不符合要求,R++;符合L++,记录min(L-R+1)即可。
完整代码
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
int n;
string s;
int cq,cw,ce,cr=0;
int coq,cow,coe,cor;
int judge()
{
for(int i=0;i<n;i++)
switch(s[i])
{
case 'Q':
cq++;
break;
case 'W':
cw++;
break;
case 'E':
ce++;
break;
case 'R':
cr++;
break;
}
coq=cq;coe=ce;cow=cw;cor=cr;
int m=(n/4);
if(cq==m&&cw==m&&ce==m&&cr==m)
return -1;
return 0;
}
int count(int L,int R)
{ cq=coq;ce=coe;cw=cow;cr=cor;
for(int i=L;i<=R;i++)
switch(s[i])
{
case 'Q':
cq--;
break;
case 'W':
cw--;
break;
case 'E':
ce--;
break;
case 'R':
cr--;
break;
}
int Max1=max(cq,cw);
int Max2=max(ce,cr);
int Max=max(Max1,Max2);
int total=R-L+1;
int free = total - (Max-cq)-(Max-cw)-(Max-ce)-(Max-cr);
if(free>=0&&free%4==0)
return R-L+1;
return -1;
}
int main()
{ cin>>s;
n=s.length();
if(judge()==-1)
{ cout<<"0"<<endl;
return 0;}
int L=0;
int c;
int Min=n;
int R=1;
while(L<n&&R<n)
{
c=count(L,R);
if(c==-1)//没找到,右指针下移
R++;
else
{Min=min(Min,c);
L++;}
if(L>R)
R=L;
}
cout<<Min<<endl;
}
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个数的最大、最小值
- 最小队列:
- 每次循环判断队首元素是不是当前窗口的(当前元素序列-队首元素序列是否<k),不是则队首弹出元素;(中间的元素因为不是最小的,不会影响到本次求最小值,所以是不是当前窗口的无所谓)
- 循环判断,如果队尾元素比要插入的元素大则弹出,直到队尾元素小于当前元素或队空,插入当前元素。储存队首元素(最小值),最后输出即可。
- 最大队列同理
完整代码
#include<iostream>
#include<cstdio>
using namespace std;
int n,k;
int Min[1000010],Max[1000010],zjm[1000010],queue[1000010];
int Minc()
{ int l=0,r=-1;
for(int i=0;i<n;i++)
{
while (l<=r&&(i-queue[l])>=k)//
l++;
while(l<=r&&zjm[i]<zjm[queue[r]])
r--;
queue[++r]=i;
Min[i]=zjm[queue[l]];
}
for(int i=k-1;i<n;i++)//第k-1才开始有效
cout<<Min[i]<<' ';
}
int Maxc()
{ int l=0,r=-1;
for(int i=0;i<n;i++)
{
while(l<=r&&(i-queue[l])>=k)//不属于当前区间
l++;
while(l<=r&&zjm[i]>zjm[queue[r]])
r--;
queue[++r]=i;
Max[i]=zjm[queue[l]];
}
for(int i=k-1;i<n;i++)
cout<<Max[i]<<' ';
}
int main()
{
scanf("%d %d",&n,&k);
for(int i=0;i<n;i++)
scanf("%d",&zjm[i]);
Minc();
cout<<endl;
Maxc();
}