「ABC247E」Max Min
蒟蒻还是第一次切掉 \(E\) 题。
题目简介
给定一个长度为 \(N\) 的序列 \(A\) 和两个数 \(X\) , \(Y\),求有多少个区间 \((a,b)\) 满足 \(\underset{i\in(a,b)}{\max}\{A_i\}=X,\underset{i\in (a,b)}{\min}\{A_i\}=Y\)
分析
有区间,有最值,想到以下两大类做法:
- 枚举左右端点 \(l,r\),判断 \((l,r)\) 是否合法。使用暴力打擂台 \(\mbox O(n^2)\), \(ST\) 表为 \(\mbox O(n^2)\),线段树为 \(\mbox O(n^2log\ n)\)
- 枚举总长度 \(len\),同时枚举左右端点 \(l,r\),类似于单调队列一样判断,\(\mbox O(n^2)\)
想一想这两种办法的劣处在哪里。
可以发现,在区间长度变化之时,两种做法均不能很好地利用先前的信息,由此思考如何改进。
不难发现,如果有一个区间 \([st,ed]\) 满足它的所有值均落在 \([X,Y]\) 内,且其子区间 \([l,r]\) 中同时拥有了 \(X\) 和 \(Y\) 。那么区间 \([l,r],[l,r+1],[l,r+2],\dots,[l,ed]\) 都一定合法,就无需再一一枚举 \(r\) ,直接从 \([l+1,r]\) 开始找就可以了。这样对于满足要求的区间 \([st,ed]\),可以 \(\mbox O(n)\) 时间算出其方案数。
剩下的只需要将区间 \([st,ed]\) 全部预处理出来就可以了。
\(AC\ Code\)
#include<cstdio>
#include<vector>
#include<iostream>
using namespace std;
int read(){
int x=0;
char ch=getchar();
while(ch<'0'||ch>'9')ch=getchar();
while(ch>='0'&&ch<='9'){
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x;
}
const int Maxn=2e5+5;
int a[Maxn];
int st[Maxn],ed[Maxn];
int main(){
int n=read(),x=read(),y=read();
int tot=1;
st[tot]=1,ed[tot]=0;
for(int i=1;i<=n;++i){
cin>>a[i];
if(a[i]<y||a[i]>x){
++tot;
st[tot]=i+1,ed[tot]=i;
continue;
}
++ed[tot];
}
long long ans=0;
for(int i=1;i<=tot;++i){
long long sum=0;
int cnt_max=0,cnt_min=0;
int l=st[i];
for(int r=st[i];r<=ed[i];++r){
if(a[r]==x)++cnt_max;
if(a[r]==y)++cnt_min;
while(cnt_max&&cnt_min){
sum+=1ll*(ed[i]-r+1);
if(a[l]==x)--cnt_max;
if(a[l]==y)--cnt_min;
++l;
}
}
ans+=sum;
}
printf("%lld\n",ans);
return 0;
}