单调栈
题目大意
给定一个直方图,求直方图中的最大矩形面积
思路
求最大矩形面积,要先求出直方图中每一个矩形的面积,实际上就是以题目中给出的矩形的高为根据,求出对应的底的长度。求底的长度,实际上就是要在横轴上找到左右端点,左端点为往左数第一个小于此高度的点(高度变小,不能形成一个矩形;只有大于等于此高度才可以);右端点为往右数第一个小于此高度的点。两个端点之间的距离,就是底的长度。而求左右端点的过程,正好可以用单调递减栈两遍处理来完成。
代码
- 这里采用的是自己手写的栈,访问删除更方便。
- 放入栈中的是每个高对应的位置,而不是具体的数值,这样更方便确定左右端点。
#include<bits/stdc++.h>
using namespace std;
long long h[100099];
int lh[100099];
int rh[100099];
int a[100099]; //单减栈
int n =0;
void solve()
{
int top=0;
long long ans=0;
for(int i=1;i<=n;i++) // 向右走,找出i右边第一个比自己小的
{
if(top==0) a[++top]=i;
else
{
while(top!=0&&h[a[top]]>h[i])
{
rh[a[top]]=i;
top--;
}
a[++top]=i;
}
}
while(top>0) //清栈
{
rh[a[top]]=n+1;
top--;
}
// for(int i=1;i<=n;i++) cout<<" "<<rh[i];
// cout<<endl;
for(int j = n ;j>=1;j--) //往左走,寻找左边第一个小于自己的元素
{
if(top==0) a[++top]=j;
else{
while(top>0&&h[a[top]]>h[j])
{
lh[a[top]]=j;
top--;
}
a[++top]=j;
}
}
while(top>0) //清栈
{
lh[a[top]]=0;
top--;
}
// for(int i=1;i<=n;i++) cout<<" "<<lh[i];
// cout<<endl;
for(int i=1;i<=n;i++)
{
ans = max(ans,h[i]*(rh[i]-lh[i]-1));
}
printf("%lld\n",ans);
}
int main()
{
// freopen("in.txt","r",stdin);
while(1)
{
scanf("%d",&n);
if(n==0)
{
break;
}
memset(lh,0,sizeof(int) *(n+10));
memset(rh,0,sizeof(int)*(n+10));
memset(a,0,sizeof(int)*(n+10));
long long tmp=0;
for(int i=1;i<=n;i++)
scanf("%lld",&h[i]);
solve();
}
return 0;
}
单调队列
题目大意
给出一个数组和一个滑动窗口,滑动窗口按数组索引从小到大方向滑动,每次滑动一个单位,求出每次滑动后的窗口中的最大数和最小数。
思路
因为现在需要寻找一个局部长度下的最大和最小值,而不是整体,所以使用单调队列更为合适。分别使用单增和单减队列,将窗口中的元素逐个放到队列中去,判断队尾元素是否小于/大于待入队元素,若不符合则从队尾出队。当窗口里的所有元素都被加入到队列之后,队首元素就是最小/最大元素。
并且,因为窗口是连续滑动的,不需要判断完一个窗口中的最小/最大值之后就清空队列(会TLE),而是通过判断即将入队的元素,和队首元素的在原数组中的位置差值,若超过滑动窗口长度,则需要将队首元素在队首出队。
代码
同样采用手写队列,队列中放入元素数组下标的方法。
#include<cstdio>
#include<cstring>
#include<iostream>
#define mem(a,b) memset(a,b,sizeof a);
#define rep(k,a,b) for(int k=a;k<=b;k++)
using namespace std;
long long a[1000099];
long long q[1000099],front=0,back=0; //单调队列,自队头向队尾有序
int k=0;
int n=0;
void solve()
{
for(int i=1;i<=n;i++)
{
while(front<back && a[q[back-1]]>a[i])
{
back--;
}
q[back++]=i;
if(i-q[front]>=k) front++; //window move
if(i>=k) //the first window dont move
{
if(i==k) printf("%lld",a[q[front]]);
else printf(" %lld",a[q[front]]);
}
}
cout<<endl;
front = back =0;
for(int i=1;i<=n;i++)
{
while(front<back && a[q[back-1]]<a[i])
{
back--;
}
q[back++]=i;
if(i-q[front]>=k) front++; //window move
if(i>=k)
{
if(i==k) printf("%lld",a[q[front]]);
else printf(" %lld",a[q[front]]);
}
}
cout<<endl;
}
int main()
{
// freopen("in.txt","r",stdin);
mem(a,0);
mem(q,0);
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)
{
scanf("%lld",a+i);
}
solve();
return 0;
}
尺取法
题目大意
给出一个长度为n 的字符串s,仅包含W,Q,E,R四个字母,如果四种字符在字符串中出现次数均为n/4,则其为一个平衡字符串,现可以将s 中连续的一段替换成任意字符,使其变为一个平衡字符串,问替换子串的最小长度?
思路
这个题之所以可以用尺取法是因为:所求解答案为一个连续区间,并且区间左右端点移动有明确方向:若区间满足要求,则可尝试L++,若不符合要求,则R++,区间越长,满足条件的机率就越大。
具体的步骤为,首先判断一下当前这个串是否已经平衡;若不平衡,则将第一个字符作为代替换子串,求除了这个子串之外的,所有四个字符出现的次数,能否通过在这个子串中替换字符,
- 使四类字符和数目最多的那一类字符相等。
- 若可以,再需判断子串中剩余的位置,能否被四个字符平均分配。
只有以上两点都满足,这个子串才是合理的,L++,去缩小子串;若不合理,则R++,扩大子串。(若R<L,需要使R++,使之重新满足L<=R)
代码
- 使用了一个map来记录各个字符的个数。
- 统计子串外四个字符出现的个数的时候,不必每次都重新统计,在以第一个字符为子串的基础上,L++,则L对应的字符个数加一(在子串之外了); R- -,则R对应的子串个数减1(变成了子串中的字符了)
#include<bits/stdc++.h>
using namespace std;
string str;
map<char,int>sum={
{'Q',0},{'E',0},{'W',0},{'R',0}
} ;
int n=0;
int l=0,r=0,ans=0;
int fmax(int a,int b,int c,int d)
{
a=a>b? a:b;
a=a>c? a:c;
a=a>d?a:d;
return a;
}
void cals()
{
for(int k=0;k<l;k++) sum[str[k]]++;
for(int k=r+1;k<n;k++) sum[str[k]]++;
}
int main()
{
// freopen("in.txt","r",stdin);
int tl=0,tr=0;
cin>>str;
n = str.size();
l=n,r=n,ans=n;
cals();
if(sum['Q']==sum['W']&&sum['E']==sum['W']&&sum['E']==sum['R'])
{
printf("%d",0);
return 0;
}
r=0;
l=0;
sum.clear();
cals();
while(r<n)
{
while(l<=r)
{
int s1=sum['Q'],s2=sum['E'],s3=sum['R'],s4=sum['W'];
int MAX =fmax(s1,s2,s3,s4);
int space = (r-l+1)-(MAX-s1)-(MAX-s2)-(MAX-s3)-(MAX-s4);
if(space>=0&&space%4==0)
{
ans = min(ans,r-l+1);
tl=l;
l++;
if(l<n) sum[str[tl]]++; //go left num++
}
else {
break;
}
}
r++;
if(r<n) sum[str[r]]--; //go right num--
// cout<<str[r]<<":"<<sum[str[r]]<<endl;
}
printf("%d",ans);
// cout<<" "<<sum['Q']<<" "<<sum['R']<<" "<<sum['W']<<" "<<sum['E']<<endl;
return 0;
}
前缀和差分
题目大意
长度为n 的数组,一共q 次操作,1 ≤ 𝑛, 𝑞 ≤ 1e5, 每次操作给出L, R , c,表示区间[L, R] 中各个数均加上c, 求q 次操作结束后,数组中各个元素值
思路
O(qn)的复杂度会超时,所以直接模拟的做法不可取。按照差分的思想,将对区间的操作,转变为对差分数组的单点操作,设原数组为A,差分数组为B,则
B
[
i
]
=
A
[
i
]
−
A
[
i
−
1
]
,
i
≥
2
B[i]=A[i]-A[i-1],i\geq 2
B[i]=A[i]−A[i−1],i≥2
∑
B
[
i
]
=
A
[
i
]
\sum B[i] = A[i]
∑B[i]=A[i]
因此,L,R 的区间上,A数组加c,对于B数组来说,只会影响B[L]和B[R+1], 由上第一式,
B[L]会增加c(
A
[
L
]
A[L]
A[L]加了c,
A
[
L
−
1
]
A[L-1]
A[L−1]没有),同理,B[R+1]会减少c,其余B数组的值不受影响(L,R区间内部,加的c两两抵消了),只需要将所有对A的操作全部转化成对B的操作,就可以O(N+q)的实现这一过程,最后再用前缀和,求出A的每一项即可。
代码
注意要用long long
#include<bits/stdc++.h>
using namespace std;
typedef long long llong; //注意了long long
int n=0;
llong a[200099];
llong b[200099];
int q=0;
int main()
{
scanf("%d%d",&n,&q);
for(int i=1;i<=n;i++) scanf("%lld",a+i);
for(int j=1;j<=n;j++)
{
if(j==1) b[1]=a[1];
else{
b[j]=a[j]-a[j-1];
}
}
int s=0,e=0,c=0;
for(int k=0;k<q;k++)
{
scanf("%d%d%d",&s,&e,&c);
b[s]+=c;
b[e+1]-=c;
}
llong sum=b[1];
for(int i=1;i<=n;i++)
{
if(i==1) printf("%lld",sum);
else{
sum+=b[i];
printf(" %lld",sum);
}
}
cout<<endl;
return 0;
}