Codeforces Round #493 (Div. 1) E. Good Subsegments 析合树

原题链接
题目描述:给定一个长度为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=l1,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;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值