[luogu3258] [JLOI2014]松鼠的新家
这道题可以用树剖或树上差分+LCA做,思路一致。每次把两个点之间的路径+1即可,但是这里是经过一次+1次,终点除外。
这里采用树上差分+LCA,LCA使用树剖维护
树上差分的思路是:每个点的权值为其子树的权值和。
修改u到v的路径,那么\(val[u]+1,val[v]+1,val[lca]-1,val[fa[lca]]-1\)
为什么是对的呢?对于这条路径,路径下的点的权值显然不会被影响,因为权值只跟子树有关。路径上的点也不会,因为两个点+1,两个点-1,没有影响。
#include <cstdio>
#include <cstring>
#include <algorithm>
#define MAXN 300005
struct edge {
int v,next;
}G[MAXN<<1];
int head[MAXN],tot = 0;
int dfn[MAXN],top[MAXN];
int size[MAXN],son[MAXN],d[MAXN],fa[MAXN];
int num = 0;
int val[MAXN],ans[MAXN];
int a[MAXN];
int N;
inline void add(int u,int v) {
G[++tot].v = v;G[tot].next = head[u];head[u] = tot;
}
void dfs1(int u,int father) {
d[u] = d[father] + 1; fa[u] = father;
size[u] = 1; son[u] = 0;
for(int i=head[u];i;i=G[i].next) {
int v = G[i].v;
if(v==father) continue;
dfs1(v,u);
size[u] += size[v];
if(size[son[u]]<size[v]) son[u] = v;
}
}
void dfs2(int u,int tp) {
top[u] = tp;dfn[u] = ++num;
if(son[u]) dfs2(son[u],tp);
for(int i=head[u];i;i=G[i].next) {
int v = G[i].v;
if(v==son[u]||v==fa[u]) continue;
dfs2(v,v);
}
}
void dfs3(int u,int father) {
ans[u] = val[u];
for(int i=head[u];i;i=G[i].next) {
int v = G[i].v;
if(v==father) continue;
dfs3(v,u);
ans[u] += ans[v];
}
}
int LCA(int u,int v) {
while(top[u]!=top[v]) {
if(d[top[u]]<d[top[v]]) std::swap(u,v);
u = fa[top[u]];
}
return d[u]<d[v] ? u : v;
}
void update(int u,int v,int w) {
val[u] += w;val[v] += w;
int lca = LCA(u,v);
val[lca] -= w;
if(fa[lca]) val[fa[lca]] -= w;
}
int main() {
std::memset(val,0,sizeof(val));
int u,v;
scanf("%d",&N);
for(int i=1;i<=N;++i) scanf("%d",&a[i]);
for(int i=1;i<N;++i) {
scanf("%d%d",&u,&v);
add(u,v);add(v,u);
}
d[0] = size[0] = 0;
dfs1(1,0);dfs2(1,0);
int now = a[1];
update(now,now,1);//计算开始节点
for(int i=2;i<=N;++i) {
int v = a[i];
update(now,v,1);//修改路径
update(now,now,-1);//起点重复计算,减去
now = v;
}
update(now,now,-1);//终点不能算
dfs3(1,0);
for(int i=1;i<=N;++i) printf("%d\n",ans[i]);
return 0;
}