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
思路
该题用到单调栈
利用单调栈的性质,正反两次遍历,找出每个高的可以延伸到的左右最大范围。
正向遍历时,因为是单调非增栈,所以当栈顶元素 > 待入栈元素,pop掉栈顶元素,直至栈顶元素 <= 待入栈元素;反向遍历操作与正向基本相同,不同是从后往前遍历的。
找右边界和左边界的函数分别用自己构造的栈和stl stack实现
注意高度范围0 <= hi <= 1000000000,与宽度进行乘法运算后有可能会超过int范围,所以要使用longlong类型(printf要%lld而不是%ll)
Answer
具体思路见注释
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<string.h>
#include<stack>
using namespace std;
const int maxn=1e5+100;
long long a[maxn];
int L[maxn],R[maxn],st[maxn];
void findR(int n){
//对于每一个位置(i),求出这个位置的高度能到达的最右边界,存在R[]数组中
int b=1,t=0;//t相当于栈顶指针 ,注意这里对于栈顶栈底的初始化
for(int i=1;i<=n;i++){
while(b<=t&&a[st[t]]>a[i]){
//st[t]存栈顶指针的下标
R[st[t]]=i-1;
t--;
}
st[++t]=i;
}
//每一个循环的意义:如原先是[4 5],现在3来了,弹出5和4后,变成[3]
while(t>0){//弹出剩余元素
R[st[t]]=n;
t--;
}
}
void findL(int n){//直接用stack实现
stack<int> p;
for(int i=n;i>=1;i--){
while(p.size()>0 && a[p.top()]>a[i]){
L[p.top()]=i+1;
p.pop();
}
p.push(i);
}
while(p.size()>0){//弹出剩余元素
L[p.top()]=1;
p.pop();
}
}
int main() {
int n;
scanf("%d",&n);
while(n!=0){
for(int i=1;i<=n;i++){
cin>>a[i];
}
findL(n);findR(n);
long long ans=0,area=0;
for(int i=1;i<=n;i++){
area=a[i]*(R[i]-L[i]+1);//注意这里要+1
ans=max(ans,area);
}
cout<<ans<<endl;
scanf("%d",&n);
}
return 0;
}
B - TT’s Magic Cat
题意
TT 有一只猫 ,它从 世界地图 选了 n 个城市,用 ai 表示每个城市的资产 。
猫会给出几个操作, 区间 [ l , r ] 的城市资产都加 c 。在q次操作后,输出所有城市的资产。
Input
第一行有两个数 n, q (1<=n,q<=2*10^5) ,n表示城市的个数,q表示操作次数。
第二行包含n个数,分别代表每个城市的资产ai。(-10^6 <=ai<=10^6)
接下来q行,每一行代表一个操作, l ,r ,c
(1≤l≤r≤n,−10^5 ≤c≤ 10^5)(1≤ l ≤ r ≤n,−10^5 ≤ c ≤ 10^5)
output
每个城市最后的资产
思路
本题暴解会TLE,而对于一个数组,将其区间内所有的数+或-某个数的时候,有两种优化方法:差分或者线段树。
差分更新的时间复杂度是O(1),查询的时间复杂度是O(n) 。
线段树更新的时间复杂度是O(logn) ,查询的时间复杂度也是O(logn) 。
因此本题是多次更新,最后一次查询,所以应使用差分来做。
关于差分:
设原数组为A,差分数组为B,范围为[1,n]
那么 B[1] = A[1]
B[i] = A[i] - A[i-1]
差分的性质
1.A[i]=SUM{B[1~i]}
2.A[L] ~ A[R] 均加上c 等价于 B[L] += c,B[R+1] -=c
B[L] 加 c,那么从L开始,往后的A 都会加上c (因为 B[L] 加了c;B[R+1] 减 c ,是目的是把B[L] 加的c抵消,从 B[R]往后,A 的值都会加c减c,所以不变。
差分之后,本题的时间复杂度从O(q*n)变为O(n+q)
Answer
具体思路见注释
#include<iostream>
#include<cstdio>
using namespace std;
const int maxn=200100;
long long a[maxn],b[maxn];
int main() {
int n,q,l,r,c;
cin>>n>>q;
for(int i=1;i<=n;i++){
cin>>a[i];
}
b[1]=a[1];
for(int i=2;i<=n;i++){
b[i]=a[i]-a[i-1];
}
for(int i=1;i<=q;i++){
scanf("%d%d%d",&l,&r,&c);
b[l]+=c;
b[r+1]-=c;
}
long long ans=b[1];
printf("%lld",ans);
for(int i=2;i<=n;i++){
ans+=b[i];
printf(" %lld",ans);
}
return 0;
}
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用于标记范围。对于选定范围,先求出该范围之外的四个字母的个数,从可用的位数里拿出来一部分对四种字母进行填平,然后再看剩下的可更改位数是否>=0且是四的倍数
本题,在[l,r]符合条件的情况下,考虑: [l,r+1] 符合,但len变大,对答案没有贡献;[l+1,r] 不一定符合,可能对答案有贡献。所以:若[l,r]符合条件,则l++;若[l,r]不符合条件,则r++。
Answer
具体思路见注释
#include<iostream>
#include<cstdio>
#include<string.h>
using namespace std;
const int maxn=1e5+100;
int n,l=1,r=0;
string s;
void Plus(){
if(s[l-1]=='Q')sum1++;
if(s[l-1]=='W')sum2++;
if(s[l-1]=='E')sum3++;
if(s[l-1]=='R')sum4++;
}
void Minus(){
if(s[r-1]=='Q')sum1--;
if(s[r-1]=='W')sum2--;
if(s[r-1]=='E')sum3--;
if(s[r-1]=='R')sum4--;
}
bool solve(){
//在[l,r]符合条件的情况下,考虑:
//[l,r+1] 符合,但len变大,对答案没有贡献
//[l+1,r] 不一定符合,可能对答案有贡献
//所以
//若[l,r]符合条件,则l++
//若[l,r]不符合条件,则r++
int maxx = max(max(sum1,sum2),max(sum3,sum4));//sum为某个字符去掉[l,r]范围内数量之后的总数
int total=r-l+1;
//从可用的位数里拿出来一部分对四种字母进行填平,令四个sum都等于最大的那一个
int free=total-((maxx-sum1)+(maxx-sum2)+(maxx-sum3)+(maxx-sum4));
//再看剩下的可更改位数是否>=0且是四的倍数
if(free>=0&&free%4==0) return 1;
else return 0;
}
void solve0(){
if(sum1==sum2&&sum2==sum3&&sum3==sum4){//特判一开始就平衡点
cout<<0;
return;
}
int ans=n+1;
while(r<=n){
while(l<=r&&solve()){
ans=min(ans,r-l+1);
Plus();
l++;
}
r++;
Minus();
}
if(ans==n+1)cout<<"lose";
else cout<<ans;
}
int main() {
cin>>s;
n=s.size();
for(int i=0;i<n;i++){
if(s[i]=='Q')sum1++;
if(s[i]=='W')sum2++;
if(s[i]=='E')sum3++;
if(s[i]=='R')sum4++;
}
solve0();
return 0;
}
D - 滑动窗口(C++和G++都交一下)
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
思路
滑动窗口,用单调队列解决,与单调栈类似
建立一个单调递增的队列,依次压入各个元素,若要压入的元素不满足队列的单调性,则从队尾元素开始移除,直到要压入的元素压入后不影响单调性。压入之后,需要判断当前范围是否超过窗口大小,若超过,则需将队首元素移除(如[1 4 5],压入2,得[1 2],但窗口大小是3,所以去掉1变为[2])。在处理后的队列中,队首元素即为滑动窗口内的最小元素。
两个函数分别用了自己构造的队列和deque实现(用deque不用queue是因为还需要删掉队尾元素)
另:
本题g++用scanf,printf时TLE了
注意:
在g++中,cout通常比printf的性能高。
影响cout的性能通常有两个因素,一是某些实现例如VS用printf实现cout,当然printf比cout快;二是iostream默认情况下是与stdio关联在一起的,就是cout在运行时,会刷新stdio,这个操作会拖慢cout的性能。但g++的cout并不是用printf实现的,即使不去掉与stdio的关联,cout已经比printf要快,如果通过std::ios_base::sync_with_stdio( false )去掉与stdio的关联,甚至比printf快几乎十倍。
Answer
具体思路见注释
#include<iostream>
#include<cstdio>
#include<queue>
using namespace std;
const int maxn=1e6+100;
int a[maxn],q1[maxn],q2[maxn];
int n,k;
void min(){
int f=1,b=0;//f为队首,b为队尾
for(int i=1;i<=n;i++){
while(b>=f&&a[q1[b]]>=a[i])b--;
q1[++b]=i;
//维护队列大小
if(q1[b]-q1[f]+1>k)f++;
if(i==k)cout<<a[q1[f]];
if(i>k)cout<<" "<<a[q1[f]];
}
}
void max(){
deque<int> q2;//注意必须用deque不能用queue,因为还需要删掉队尾元素
for(int i=1;i<=n;i++){
while(q2.size()>0&&a[q2.back()]<=a[i])q2.pop_back();
q2.push_back(i);
//维护队列大小
if(q2.back()-q2.front()+1>k)q2.pop_front();
if(i==k)cout<<a[q2.front()];
if(i>k)cout<<" "<<a[q2.front()];
}
}
int main() {
cin>>n>>k;
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
min();
printf("\n");
max();
return 0;
}