【ZJOI2017】【LOJ2570】【洛谷P5210】【BZOJ4876】线段树(树上差分(雾)

LOJ传送门

洛谷传送门

BZOJ传送门


题解:

仿照ZKW线段树的定位方式,我们可以推出一个很显然的提取区间的方式:左右端点化为开区间,找到开区间端点对应的叶子节点,取出两个叶子的LCA,提取左端点一路向上的所有点的没有被访问的右儿子,右端点操作同理。提取出的节点集合就是我们定位区间 [ l , r ] [l,r] [l,r]得到的节点。

那么我们现在需要计算点 u u u到所有我们定位出的点的距离和。

很显然,LCA到根的路径被左右端点到根的路径包含了,于是可以在线段树上利用差分进行计算了。

记录一下每个点到根的路径上分别要经过几个左儿子几个右儿子,经过的左右儿子的深度和是多少。

现在考虑怎么计算 u u u l l l到根的所有定位点的距离和。

显然我们可以利用一下基础的差分求距离式子: d i s t ( u , v ) = d e p [ u ] + d e p [ v ] − 2 ∗ d e p [ l c a ] dist(u,v)=dep[u]+dep[v]-2*dep[lca] dist(u,v)=dep[u]+dep[v]2dep[lca]

发现各自的深度之和这个很好维护,但是我们要维护LCA的深度之和。。。

我们取出 u u u l l l的LCA,我们发现,在LCA继续向上的路径中,LCA才会变化。

路径分两段计算一下就行了。

复杂度:预处理 O ( n ) O(n) O(n),单次询问 O ( log ⁡ n ) O(\log n) O(logn)


代码:

#include<bits/stdc++.h>
#define ll long long
#define re register
#define gc get_char
#define cs const

namespace IO{
	inline char get_char(){
		static cs int Rlen=1<<22|1;
		static char buf[Rlen],*p1,*p2;
		return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++; 
	}
	
	template<typename T>
	inline T get(){
		re char c;
		while(!isdigit(c=gc()));re T num=c^48;
		while(isdigit(c=gc()))num=(num+(num<<2)<<1)+(c^48);
		return num;
	}
	inline int getint(){return get<int>();}
}
using namespace IO;

using std::cerr;
using std::cout;

cs int N=4e5+5,M=2e5+5;

int n,m;

namespace SGT{
	int pos[N];
	int son[N][2],cnt;
	
	int L[N],R[N];
	int fa[N],dep[N],ch[N],top[N];
	
	inline int build(int l,int r){
		int cur=++cnt;
		L[cur]=l,R[cur]=r;
		if(l==r)return pos[l]=cur;
		int mid=getint();
		son[cur][0]=build(l,mid);
		son[cur][1]=build(mid+1,r);
		ch[cur]=(mid-l+1>r-mid)?son[cur][0]:son[cur][1];
		return cur;
	}
	
	int sum[N][2];
	ll sd[N][2];
	void dfs(int u){
		if(!son[u][0])return ;
		{	
			int v=son[u][0];
			fa[v]=u;dep[v]=dep[u]+1;
			top[v]=v==ch[u]?top[u]:v;
			sum[v][0]=sum[u][0]+1;
			sum[v][1]=sum[u][1];
			sd[v][0]=sd[u][0]+dep[u]+1;
			sd[v][1]=sd[u][1];
			dfs(v);
		}
		{
			int v=son[u][1];
			fa[v]=u;dep[v]=dep[u]+1;
			top[v]=v==ch[u]?top[u]:v;
			sum[v][0]=sum[u][0];
			sum[v][1]=sum[u][1]+1;
			sd[v][0]=sd[u][0];
			sd[v][1]=sd[u][1]+dep[u]+1;
			dfs(v);
		}
	}
	
	inline void init(){
		build(1,n);
		pos[0]=++cnt,pos[n+1]=++cnt;
		int root=++cnt;L[root]=0,R[root]=n+1;
		son[root][0]=pos[0],son[root][1]=ch[root]=++cnt;
		son[cnt][0]=ch[cnt]=1,son[cnt][1]=pos[n+1];
		L[cnt]=1,R[cnt]=n+1;
		top[root]=root,dfs(root);
	}
	
	inline int LCA(int u,int v){
		while(top[u]^top[v])dep[top[u]]>dep[top[v]]?u=fa[top[u]]:v=fa[top[v]];
		return dep[u]>dep[v]?v:u;
	}
	
	inline bool insub(int u,int v){
		return L[u]<=L[v]&&R[v]<=R[u];
	}
	
	inline ll get(int p,int u,bool op){
		int lca=LCA(p,u);
		ll now=(ll)dep[lca]*(sum[p][op]-sum[lca][op])+(insub(son[lca][op],p)&&lca!=u)+sd[lca][op]-sum[lca][op];
		return sd[p][op]+(ll)sum[p][op]*dep[u]-now*2;
	}
	
	inline ll calc(int l,int r,int u){
		int lca=LCA(l=pos[l],r=pos[r]);
		ll res=0;
		res+=get(l,u,0)-get(son[lca][0],u,0);
		res+=get(r,u,1)-get(son[lca][1],u,1);
		return res;
	}
}


signed main(){
//	freopen("segment.in","r",stdin);
	n=getint();
	SGT::init();
	m=getint();
	while(m--){
		int u=getint(),l=getint()-1,r=getint()+1;
		cout<<SGT::calc(l,r,u)<<"\n";
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值