原题链接
题目描述:给定一个长度为n的排列,每次询问一个区间,有多少个子区间是连续段
解法:建出析合树,考虑询问区间是
[
l
,
r
]
[l,r]
[l,r],设
L
=
l
−
1
,
R
=
r
+
1
;
K
=
l
c
a
(
L
,
R
)
L=l-1,R=r+1; K=lca(L,R)
L=l−1,R=r+1;K=lca(L,R)。K的儿子中L的祖先为
S
L
SL
SL,R的祖先为
S
R
SR
SR。
需要统计以下信息:
t
o
t
tot
tot:以
i
i
i为根的子树内连续段数目
l
n
ln
ln:
i
i
i所在的儿子序列中,只使用下标小于
i
i
i的儿子及其后代组成的连续段数目
r
n
rn
rn:
i
i
i所在的儿子序列中,只使用下标大于
i
i
i的儿子及其后代组成的连续段数目
我们根据是否会对询问产生贡献将节点分为两类:内部节点和外部节点。考虑从
L
L
L,
R
R
R向上走到
K
K
K,路径上经过的所有节点都不会出现在答案里,这些节点以及更加外层的节点为外部节点,位于经过路径内部的为内部节点,可以对答案产生贡献。具体来说,对于从
S
L
SL
SL的儿子到
L
L
L的路径,我们统计它们的
∑
r
n
i
\sum rn_i
∑rni,对于从
S
R
SR
SR的儿子到
R
R
R的路径,我们统计
∑
l
n
i
\sum ln_i
∑lni,对于
S
L
SL
SL和
S
R
SR
SR所在的这一层,我们单独统计,可以预处理一个
s
u
m
sum
sum表示
t
o
t
tot
tot的前缀和。使用树上差分即可实现链信息查询。
复杂度
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<string>
#include<cmath>
#include<queue>
#include<map>
#include<bitset>
#define N 300032
#include<deque>
#include<cstdlib>
#include<set>
#include<ctime>
#define ll long long
#define mp make_pair
using namespace std;
ll read()
{
ll x=0,f=1;
char c=getchar();
while(c>'9'||c<'0')
{
if(c=='-') f=-1;
c=getchar();
}
while(c>='0'&&c<='9')
{
x=x*10+c-'0';
c=getchar();
}
return f*x;
}
int n,a[N],st[N][2],top[2],L[N],cnt,pl[N],pr[N],jp[N][20],nd[N],num,dep[N];
ll sln[N],srn[N];
bool is[N];
vector<int>son[N];
void add(int u,int v)
{//儿子需有序
son[u].push_back(v);
}
struct TREE{
int add,mn;
}tr[N<<2];//mx-mn+l-1=r
struct SGT{
int l,r,posl,posr;
bool op;//合点为0
bool rs;//儿子排列是否为升序
int sonid;//所在儿子序列的编号
ll tot,ln,rn,sum;
}sgt[N<<2];
void dfs1(int x,int fa)
{
dep[x]=dep[fa]+1;
sgt[x].tot=1;
ll tt=son[x].size();
for(int i=0;i<tt;i++){
int v=son[x][i];
dfs1(v,x);
sgt[x].tot+=sgt[v].tot;
sgt[v].sonid=i;
if(i)
{
sgt[v].ln=sgt[son[x][i-1]].ln+sgt[son[x][i-1]].tot;
if(!sgt[x].op) sgt[v].ln+=i-1;
sgt[v].sum=sgt[son[x][i-1]].sum+sgt[v].tot;
}
else sgt[v].sum=sgt[v].tot;
}
for(int i=tt-2;i>=0;--i)
{
int v=son[x][i];
sgt[v].rn=sgt[son[x][i+1]].rn+sgt[son[x][i+1]].tot;
if(!sgt[x].op) sgt[v].rn+=tt-i-2;
}
if(!sgt[x].op) sgt[x].tot+=(tt-1)*tt/2-1;
}
void dfs2(int x,int fa)
{
srn[x]=srn[fa]+sgt[x].rn;
sln[x]=sln[fa]+sgt[x].ln;
int tt=son[x].size();
for(int i=0;i<tt;++i)
{
int v=son[x][i];
dfs2(v,x);
}
}
void pushup(int x)
{
int l=x<<1,r=x<<1|1;
tr[x].mn=min(tr[l].mn,tr[r].mn);
}
void pushdown(int x)
{
int l=x<<1,r=x<<1|1;
if(tr[x].add)
{
int k=tr[x].add;
tr[x].add=0;
tr[l].add+=k;tr[l].mn+=k;
tr[r].add+=k;tr[r].mn+=k;
}
}
void build(int rt,int l,int r)
{
if(l==r)
{
tr[rt].mn=l;
return ;
}
int mid=l+r>>1;
build(rt<<1,l,mid);
build(rt<<1|1,mid+1,r);
pushup(rt);
}
void update(int rt,int l,int r,int first,int end,int k)
{
if(first<=l&&r<=end)
{
tr[rt].add+=k;
tr[rt].mn+=k;
return ;
}
pushdown(rt);
int mid=l+r>>1;
if(first<=mid) update(rt<<1,l,mid,first,end,k);
if(end>mid) update(rt<<1|1,mid+1,r,first,end,k);
pushup(rt);
}
int calc(int rt,int l,int r,int first,int end,int k)
{//线段树二分
if(first>end) return 0;
if(l==r)
{
if(tr[rt].mn==k) return l;
else return 0;
}
int mid=l+r>>1;
pushdown(rt);
int asr=0,asl=0;
if(first<=mid&&tr[rt<<1].mn==k) asl=calc(rt<<1,l,mid,first,end,k);
if(asl) return asl;
if(end>mid&&tr[rt<<1|1].mn==k) asr=calc(rt<<1|1,mid+1,r,first,end,k);
return asr;
}
bool cmp(int x,int y)
{
return sgt[x].posl<sgt[y].posl;
}
void insert(int now,int pos)
{
int R=sgt[now].r;
if(num==0||L[pos]==sgt[now].posl)//第4种情况,直接入栈
{
nd[++num]=now;
return ;
}
if(sgt[nd[num]].r+1==sgt[now].l||sgt[nd[num]].l==sgt[now].r+1)//能与前一节点合并,1,2情况
{
int tmp=nd[num--];
if(!sgt[tmp].op&&(sgt[tmp].rs&&sgt[now].l==sgt[tmp].r+1||!sgt[tmp].rs&&sgt[tmp].l==sgt[now].r+1))//前一个点为合点,且满足儿子顺序
{
jp[now][0]=tmp;
add(tmp,now);
if(sgt[tmp].rs) sgt[tmp].r=sgt[now].r;
else sgt[tmp].l=sgt[now].l;
sgt[tmp].posr=sgt[now].posr;
insert(tmp,pos);
}
else//前一个点为析点或者叶子
{//新建合点
cnt++;
sgt[cnt].op=0;
sgt[cnt].l=min(sgt[tmp].l,sgt[now].l);
sgt[cnt].r=max(sgt[tmp].r,sgt[now].r);
sgt[cnt].rs=sgt[now].r>sgt[tmp].r;
sgt[cnt].posl=sgt[tmp].posl;
sgt[cnt].posr=sgt[now].posr;
jp[now][0]=jp[tmp][0]=cnt;
add(cnt,tmp);
add(cnt,now);
insert(cnt,pos);
}
}
else//新建析点 3情况
{
int tot=sgt[now].r-sgt[now].l+1;
cnt++;
sgt[cnt].op=1;
jp[now][0]=cnt;
add(cnt,now);
sgt[cnt].l=sgt[now].l;
sgt[cnt].r=sgt[now].r;
sgt[cnt].posr=sgt[now].posr;
do{
int tmp=nd[num--];
jp[tmp][0]=cnt;
add(cnt,tmp);
tot+=sgt[tmp].r-sgt[tmp].l+1;
sgt[cnt].l=min(sgt[cnt].l,sgt[tmp].l);
sgt[cnt].r=max(sgt[cnt].r,sgt[tmp].r);
sgt[cnt].posl=sgt[tmp].posl;
}while(tot!=sgt[cnt].r-sgt[cnt].l+1);
sort(son[cnt].begin(),son[cnt].end(),cmp);
insert(cnt,pos);
}
}
void work(int x,int y)
{
ll as=0;
int inx=x,iny=y;
if(dep[x]>dep[y])
{
for(int i=18;i>=0;--i)
if(dep[jp[x][i]]>=dep[y]) x=jp[x][i];
}
else
{
for(int i=18;i>=0;--i)
if(dep[jp[y][i]]>=dep[x]) y=jp[y][i];
}
for(int i=18;i>=0;--i) if(jp[x][i]!=jp[y][i]) x=jp[x][i],y=jp[y][i];
as=srn[inx]-srn[x]+sln[iny]-sln[y];
int idx=sgt[x].sonid,idy=sgt[y].sonid,fa=jp[x][0];
as+=sgt[son[fa][idy-1]].sum-sgt[x].sum;
if(!sgt[fa].op)
{
ll tmp=idy-1-idx;
if(tmp>0) as+=tmp*(tmp-1)/2;
}
cout<<as<<'\n';
}
int main()
{
n=read();
for(int i=2;i<=n+1;++i) a[i]=read();
a[1]=n+1;
a[n+2]=n+2;
n+=2;
build(1,1,n);
for(int i=1;i<=n;++i)
{
while(top[0]&&a[i]<a[st[top[0]][0]])
{
update(1,1,n,st[top[0]-1][0]+1,st[top[0]][0],a[st[top[0]][0]]-a[i]);
top[0]--;
}
st[++top[0]][0]=i;
while(top[1]&&a[i]>a[st[top[1]][1]])
{
update(1,1,n,st[top[1]-1][1]+1,st[top[1]][1],a[i]-a[st[top[1]][1]]);
top[1]--;
}
st[++top[1]][1]=i;
L[i]=calc(1,1,n,1,i,i);
}
cnt=n;
for(int i=1;i<=n;++i)
{
sgt[i].l=sgt[i].r=a[i];
sgt[i].posl=sgt[i].posr=i;
sgt[i].op=1;//认为叶子为析点
insert(i,i);
}
for(int i=1;i<=18;++i)
for(int j=1;j<=cnt;++j) jp[j][i]=jp[jp[j][i-1]][i-1];
dfs1(nd[1],0); //最后在栈里的节点为根节点
dfs2(nd[1],0);
int q=read();
while(q--)
{
int x=read(),y=read()+2;
work(x,y);
}
return 0;
}