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;
}

浙公网安备 33010602011771号