SMOJ 树

给一个 \(n\) 个节点的树,树上的每个节点都有一个权值 \(a_i\),有 \(q\) 次询问,每次选两个点 \(u,v\) ,考虑简单路径\(u,v\)上点组成的集合\(S\),求\(\sum_{w\in S}{a_w \ or\ dist(u,w)}\)

\(n,q \le 200000\)


分析问题

首先考虑将路径 \((u,v)\) 拆分为 \((u,lca)\)\((lca,u)\),前者是一条向上的路径,后者则是向下的路径。


解决问题

  • \(u-lca\)

    先考虑 \((u,lca)\) ,向上的路径可以用倍增分成 \(log_2 n\) 个块。

    \(u\)\(lca\) 深度差为 \(dep\) ,则这条路径上有 \(dep+1\) 个点。

    我们按照倍增的思想从大到小分成若干段,每次分出的段的长度是大于当前长度的一半的,比如有 \(7\) 个点,我们首先会分出一个 \(2^2\) 的块和剩下的部分。

    但是求答案并不是简单的相加,你前面 \(2^2\) 会按位或 \(1-4\) , 但是后面并不是会或上 \(1-3\) 而是 \(5-7\) ,这之间的差距是少或了 \(2^2\)

    因为每次分出长度都是之前的一半以上,所以若当前分出了长度为 \(2^i\) 的段,剩下部分就一定会或上 \(2^i\)

    可以通过树上差分预处理出一段路径上所有的 \(a_u\) 中第 \(i\) 位为 \(0\) 的有多少个,记根到点 \(u\) 的所有 \(a_v\)\(i\) 位为 \(0\) 的个数为 \(sum_{u,i}\)

    \(hi_{u,i}\)为 以 \(u\) 为起点,向上 \(2^i\) 个点组成的路径的答案和。

    \(fa_{u,i}\)\(u\) 的第 \(2^i\) 级祖先。

    所以有 $ans_{u-lca}=hi_{u,i}+ans_{fa_{u,i}-lca}+2^{i}*(sum_{fa_{u,i},i} - sum_{fa_{lca,0},i}) $

    预处理 \(hi_{u,i}\) 也很简单,同理可得,
    \(hi_{u,i}=hi_{u,i-1} +hi_{fa_{u,i-1},i-1}+2^{i-1}*(sum_{fa_{u,i-1},i-1} -sum_{fa_{u,i},i-1})\)

  • \(lca-v\)

    向下的路径比较麻烦,首先我们可以像预处理 \(hi_{u,i}\) 一样预处理出一个向下的路径 \(low_{u,i}\)

    可得\(low_{u,i}=low_{u,i-1} +low_{fa_{u,i-1},i-1}+2^{i-1}*(sum_{u,i-1}-sum_{fa_{u,i-1},i-1})\)

    但是由于起点不是的权值不是 \(0\) 而是 \(dep_u-dep_v\),所以我们要先转化一下。

    \(d=dep_u-dep_v\) ,先求出 \(lca\) 的 第 \(d\) 级祖先,设为 \(Fa\) , 那么现在就转化成了以 \(Fa\) 为起点的两条路径 \(Fa-v\)\(Fa-lca\) , 它们的答案相减即是 \(lca-v\) (由于 \(lca\) 这个点在 \(u-lca\) 已经算过了,所以此处可以减去)

    现在就是要求一条向下的路径的答案和。

    考虑类似于向上的路径的方法,每次挑出长度为 \(2^i\) 且大于当前长度一半的一段路径,剩下的都或上 \(2^i\)

    但是由于从上往下的路径,所以从下往上推的时候我们要从小到大分段。

    类似于从上往下的计算方法即可。

    每次需要或上 \(2^i\) 的是从 \(v\) 开始向上若干深度的一段。


细节

由于向上跳若干级祖先可能没有,所以要现在根上面连一条长度为 \(n\) 的链。


代码

#include<bits/stdc++.h>

using namespace std;
int sum[600005][21];
int fa[600005][21];
long long hi[600005][21],low[600005][21];
int a[600005],dep[600005];
int n,q;
struct edge{
	int obj;
	int last;
}e[1200005];
int head[600005],g;
void link(int u,int v){
	e[++g].obj=v;
	e[g].last=head[u];
	head[u]=g;
}
void dfs(int u,int F){
//	cout<< u<<endl;
	dep[u]=dep[F]+1;
	for(int i=0;i<=20;i++){
		sum[u][i]=sum[F][i];
		if(!((1<<i)&a[u]))sum[u][i]++;
	}
	hi[u][0]=a[u];
	low[u][0]=a[u];
	fa[u][0]=F;
	for(int i=1;i<=20;i++)
		if(fa[fa[u][i-1]][i-1])fa[u][i]=fa[fa[u][i-1]][i-1];
		else break;
	for(int i=1;i<=20;i++){
		hi[u][i]=hi[u][i-1]+hi[fa[u][i-1]][i-1]+1ll*(sum[fa[u][i-1]][i-1]-sum[fa[fa[u][i-1]][i-1]][i-1])*(1<<i-1);
		low[u][i]=low[u][i-1]+low[fa[u][i-1]][i-1]+1ll*(sum[u][i-1]-sum[fa[u][i-1]][i-1])*(1<<i-1);
	}
	for(int i=head[u];i;i=e[i].last){
		int v=e[i].obj;
		if(v==F)continue;
		dfs(v,u);
	}
}
int LCA(int u,int v){
	if(dep[u]<dep[v])swap(u,v);
	for(int i=20;i>=0;i--)
		if(dep[u]-(1<<i)>=dep[v])u=fa[u][i];
	if(u==v)return u;
	for(int i=20;i>=0;i--)
		if(fa[u][i]!=fa[v][i])u=fa[u][i],v=fa[v][i];
	return fa[u][0]; 
}
long long calc(int u,int F){
	int dt=dep[u]-dep[F]+1;
	long long ans=0;
	for(int i=20;i>=0;i--)
		if(dt&(1<<i)){
			ans+=hi[u][i];
			u=fa[u][i];
			ans+=1ll*(1<<i)*(sum[u][i]-sum[fa[F][0]][i]);
		}
	return ans;
}
int Find(int u,int d){
	for(int i=20;i>=0;i--)
		if(d&(1<<i))u=fa[u][i];
	return u;
}
long long calc2(int u,int F){
	int dt=dep[u]-dep[F]+1;
	long long ans=0;
	int lu=u;
	for(int i=0;i<=20;i++){
		if(dt&(1<<i)){
			ans+=low[u][i];
			ans+=1ll*(sum[lu][i]-sum[u][i])*(1<<i);
			u=fa[u][i];
		}
	}
	return ans;
}
int main(){
	freopen("tree.in","r",stdin);
	freopen("tree.out","w",stdout);
	scanf("%d%d",&n,&q);
	for(int i=1;i<=n;i++)scanf("%d",&a[i]);
	for(int i=1;i<n;i++){
		int u,v; 
		scanf("%d%d",&u,&v);
		link(u,v);
		link(v,u);
	}
	link(n+1,1);
	for(int i=n+2;i<=n*2;i++)link(i,i-1);
	dfs(2*n,0);
	for(int i=1;i<=q;i++){
		int u,v;
		scanf("%d%d",&u,&v);
		int lca=LCA(u,v);
		long long ans=calc(u,lca);
		u=Find(lca,dep[u]-dep[lca]);
		ans+=calc2(v,u);
		ans-=calc2(lca,u);
		printf("%lld\n",ans);
	}
	return 0;
}

posted @ 2022-10-11 16:33  天穹の流星  阅读(59)  评论(0)    收藏  举报