bzoj 3631 (树上差分)
传送门
题意:
给你一棵有个结点的树,现在给你一个大小为的排列,说明你的行走路径。你每经过树上的每一个点,你就需要将这个点的点权加1。问你最后所有点的点权大小。
题目分析:
根据题目的意思,很明显这道题是一个非常典型的点差分的问题。我们只需要对结点,以及,进行差分之后即可。
最后我们还需要注意的是,上述的差分过程中,会对那些既属于起点,又属于终点的点会被重复经过一次。因此我们只需要在最后把他们即可。
代码:
#include <bits/stdc++.h>
using namespace std;
const int maxn=600005;
const int LOG=20;
struct Node{
int to,next;
}q[maxn<<1];
int head[maxn],cnt=0;
void add_edge(int from,int to){
q[cnt].to=to;
q[cnt].next=head[from];
head[from]=cnt++;
}
struct LCA{//倍增lca
int anc[maxn][LOG],depth[maxn];
void dfs(int x,int fa,int dis){
anc[x][0]=fa;depth[x]=dis;
for(int i=head[x];i!=-1;i=q[i].next){
int to=q[i].to;
if(to==fa) continue;
dfs(to,x,dis+1);
}
}
void init(int root,int n){
dfs(root,-1,1);
for(int j=1;j<LOG;j++){
for(int i=1;i<=n;i++){
anc[i][j]=anc[ anc[i][j-1] ][j-1];
}
}
}
void swim(int &x,int h){
for(int i=0;h>0;i++){
if(h&1)
x=anc[x][i];
h>>=1;
}
}
int query(int x,int y){
if(depth[x]<depth[y]) swap(x,y);
swim(x,depth[x]-depth[y]);
if(x==y) return x;
for(int i=LOG-1;i>=0;i--){
if(anc[x][i]!=anc[y][i]){
x=anc[x][i];
y=anc[y][i];
}
}
return anc[x][0];
}
}lca;
int Bit[maxn];
int a[maxn];
void Search(int x,int fa){//最优一次dfs获取权值
for(int i=head[x];i!=-1;i=q[i].next){
int to=q[i].to;
if(to==fa) continue;
Search(to,x);
Bit[x]+=Bit[to];
}
}
int main()
{
int n,m;
scanf("%d",&n);
memset(head,-1,sizeof(head));
cnt=0;
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
for(int i=1;i<n;i++){
int from,to;
scanf("%d%d",&from,&to);
add_edge(from,to);
add_edge(to,from);
}
lca.init(1,n);
for(int i=2;i<=n;i++){
Bit[a[i-1]]++;
Bit[a[i]]++;
int x=lca.query(a[i-1],a[i]);
Bit[x]--;
Bit[lca.anc[x][0]]--;
}
Search(1,-1);
for(int i=2;i<=n;i++){
Bit[a[i]]--;
}
for(int i=1;i<=n;i++){
printf("%d\n",Bit[i]);
}
return 0;
}