Trip
Time Limits: 1500 ms Memory Limits: 262144 KB
题目大意
给定一个数列a[1..n]
|a[i]|≤109
,数列中的数两两不同,给定m个询问,每个询问l,r,求存在多少个
i∈[l,r]
满足
∀j∈[l,i)a[j]<a[i]
或者
∀j∈(j,r]a[j]<a[i]
输入
第一行两个整数n,m。
接下来一行n个整数,第i个是a[i]。
接下来m行,第i行两个整数Li,Ri。
输出
m行,第i行表示第i组游客去的景点个数。
样例
Input
6 4
3 1 7 4 5 2
1 5
2 5
2 6
4 6
Sample Output
3
3
4
3
数据范围
30%:N,M≤5,000
60%:N,M≤100,000
100%:N,M≤1,000,000 0≤|a[i]|≤1,000,000,000 1≤Li≤Ri≤N
提示
第一组游客选择路段的景点评估值序列为[3,1,7,4,5],其中3,7,5满足条件
第二组游客选择路段的景点评估值序列为[1,7,4,5],其中1,7,5满足条件
第三组游客选择路段的景点评估值序列为[1,7,4,5,2],其中3,7,5,2满足条件
第四组游客选择路段的景点评估值序列为[4,5,2],其中4,5,2满足条件
本题数据规模较大,请注意您的常数造成的影响。
解题思路
60%
考虑a[i]能对那些期间有贡献
我们找出两个位置j,k其中
j<i<k
其中
∀p∈(j,i)a[p]<a[i]
,
∀p∈(i,k)a[p]<a[i]
那么i能产生贡献的区间[l,r]要满足
j<l<i,r>i
或
i<r<k,l<i
那么我们正着反着两次去扫,拿正着的说,反着的反之:
i从小到大扫,相应求出j,a[i]能对
l∈(j,i]
区间做贡献,那么就将
(j,i]
区间加1,用个数据结构维护
当i扫到i+1时,r=i的区间就会作废,那么直接求l上的值,计入答案就好了
由于这种方法,区间最大值会计算两次,最后-1就行
O(nlogn)
100%
建棵大根笛卡尔树:
也就是说树上的所有节点x满足x的下标大于左子树中所有节点的下标,小于所有右子树节点的下标,x的权值小于子树所有节点的权值
O(n)法:
假设当前子树代表区间[h,t],那么这棵子树的根节点x一定是区间[h,t]中权值最大的位置,它的左节点l是区间[h,x)中最大的比a[x]小的位置,右节点r同理
先找连向左节点的边:
因为一个点向左连边只可能连向它左边比它小的最大数位置,从1到n扫过去,维护一个栈,从栈底到栈顶数呈上升,扫到i时,将a[i]丢进栈,把小于a[i]的位置弹栈,左后弹栈的位置即为小于a[i]且最大的位置,i认它为左节点
右节点的反过来找
void maketree(){
a[0]=1000000001;sta[0]=0;top=0;
for(int i=1;i<=n;i++){
if(a[sta[top]]<a[i]){
while(a[sta[top]]<a[i])top--;
fa[sta[top+1]]=i;son[i][0]=sta[top+1];
}sta[++top]=i;
}top=0;
for(int i=n;i;i--){
if(a[sta[top]]<a[i]){
while(a[sta[top]]<a[i])top--;
fa[sta[top+1]]=i;son[i][1]=sta[top+1];
}sta[++top]=i;
}
for(root=1;fa[root]!=0;root=fa[root]);
}
对于区间[l,r],最大值的位置就是l和r的lca,那么我们要求的是[l,r]中在路径(l,r)中的个数:1.lca 2.路径(l,lca)不包括lca为左节点 3.(r,lca)不包括lca为右节点
用个前缀和维护就好了
建树
O(n)
tarjanlca
O(nα(n))
询问
O(m)
总共是
O(nα(n))
但是怕tarjanlca会爆栈,又懒得打人工栈,换用了链剖lca,复杂度远小于
O(nlogn)
code:
#include<cstring>
#include<cstdio>
#define N 1001001
using namespace std;
int sta[N],a[N],n,m,fa[N],son[N][2],firx[N],firy[N],v[N],top,que[N],head,tail,rv[N],lv[N],root,Top[N],heavy[N],deep[N],siz[N];
inline int read(){
int x=0,sig=1;char c;for(;(c=getchar())<'0' || c>'9';)if(c=='-')sig=-1;
for (;c>='0' && c<='9';c=getchar())x=(x<<1)+(x<<3)+c-48;return x*sig;
}
inline void write(int x){
int top=0;char ch[10];for(;x;x/=10)ch[++top]=x%10+48;
if(!top)ch[top=1]='0';while(top)putchar(ch[top--]);
putchar('\n');
}
inline void maketree(){
a[0]=1000000001;sta[0]=0;top=0;
for(int i=1;i<=n;i++){
if(a[sta[top]]<a[i]){
while(a[sta[top]]<a[i])top--;
fa[sta[top+1]]=i;son[i][0]=sta[top+1];lv[sta[top+1]]=1;
}sta[++top]=i;
}top=0;
for(int i=n;i;i--){
if(a[sta[top]]<a[i]){
while(a[sta[top]]<a[i])top--;
fa[sta[top+1]]=i;son[i][1]=sta[top+1];rv[sta[top+1]]=1;
}sta[++top]=i;
}
for(root=1;fa[root]!=0;root=fa[root]);rv[root]=1;
}
inline void makelink(){
for(head=0,siz[que[tail=1]=root]=1,deep[root]=1;head!=tail;){
head++;int now=que[head],l=son[now][0],r=son[now][1];
if(l)rv[l]+=rv[now],lv[l]+=lv[now],siz[que[++tail]=l]=1,deep[l]=deep[now]+1;
if(r)rv[r]+=rv[now],lv[r]+=lv[now],siz[que[++tail]=r]=1,deep[r]=deep[now]+1;
}
for(int i=tail;i;i--){
int x=que[i];
siz[x]+=siz[son[x][0]]+siz[son[x][1]];
if(son[x][0] && (!son[x][1] || siz[son[x][0]]>=siz[son[x][1]]))heavy[x]=son[x][0];
else if(son[x][1])heavy[x]=son[x][1];
}
for(head=0,Top[que[tail=1]=root]=root;head!=tail;){
head++;int now=que[head],l=son[now][0],r=son[now][1];
if(l && l==heavy[now])Top[que[++tail]=l]=Top[now];else if(l)Top[que[++tail]=l]=l;
if(r && r==heavy[now])Top[que[++tail]=r]=Top[now];else if(r)Top[que[++tail]=r]=r;
}
}
int main(){
freopen("trip.in","r",stdin);
freopen("trip.out","w",stdout);
n=read();m=read();
for(int i=1;i<=n;i++)a[i]=read();
maketree();makelink();
for(int i=1,x,y;i<=m;i++){
x=read(),y=read();
int lca;
for(int x_=x,y_=y;;){
if(Top[x_]==Top[y_]){if(deep[x_]>deep[y_])lca=y_;else lca=x_;break;}
if(deep[Top[x_]]>deep[Top[y_]])x_=fa[Top[x_]];else y_=fa[Top[y_]];
}
write(1+rv[y]-rv[lca]+lv[x]-lv[lca]);
}
fclose(stdin);fclose(stdout);
return 0;
}