[NOIP2016] 天天爱跑步
前言
看这道题不爽很久了,但一直没有开它,原因是我不会(我太菜了),看了题解还是写不来,因为我不会线段树合并。
然后今天学了dsu on tree这种神奇的科技,成功把它A了,效率吊打线段树合并。
于是写篇题解纪念一下。
题目链接
算法概述
不带修改的树上路径信息的维护,很容易想到树上差分。
我们考虑一条路径,会对哪些点产生贡献,也就是题目描述中的一个人会被哪些观察员观察到。
假设这条路径为 \((s,t)\),点为 \(x\),我们分两部分考虑:
-
若点 \(x\) 在 \(s\) 到 \(lca(s,t)\)(含)的路径上,则若满足 \(w[x]=dep[s]-dep[x]\) 则会在 \(x\) 上产生 \(1\) 的贡献。
将式子变形:\(w[x]+dep[x]=dep[s]\),我们就可以发现一个事情,对于任何一个点 \(p\),这种情况的点数即为所有路径中起点深度为 \(w[p]+dep[p]\) 的数量。
我们可以形象化地理解为,有 \(m\) 个人,每个人会给 \(s\) 到 \(lca(s,t)\) 路径上的每个点发放一个类型为 \(dep[s]\) 的物品,而我们要求的就是对于每个点 \(x\),其上类型为 \(w[x]+dep[x]\) 的物品有多少个。
再抽象一点,即一条路径 \(s→lca(s,t)\),会将路径上每个点处权值为 \(dep[s]\) 的位置覆盖一次,最终求每个点 \(x\) 上权值为 \(w[x]+dep[x]\) 的位置被覆盖了几次。
此时,树上差分已经呼之欲出了。我们在每个点上开一个桶,对于每条路径 \((s,t)\),在点 \(s\) 让下标为 \(dep[s]\) 的值\(+1\),在点 \(fa[lca(s,t)]\) 上让下标为 \(dep[s]\) 的值 \(-1\),其中 \(fa[i]\) 表示 \(i\) 的父亲节点,最后每个点的信息应该是以其为根的子树和,这部分答案即为该点的桶内下标为 \(w[x]+dep[x]\) 的值。
-
若点 \(x\) 在 \(lca(s,t)\)(不含)的路径上,设路径长度为 \(dis\),则若满足 \(dis-(dep[t]-dep[x])=w[x]\),根据 \(dis=dep[s]+dep[t]-2*dep[lca(s,t)]\),可将式子变形为:\(w[x]-dep[x]=dep[s]-2*dep[lca(s,t)]\)。
那么同样应用树上差分,在点 \(t\) 上使下标为 \(dep[s]-2*dep[lca(s,t)]\) 的值 \(+1\),在 \(lca(s,t)\) 上使下标为 \(dep[s]-2*dep[lca(s,t)]\) 的值 \(-1\),最后每个点的信息统计子树和,答案即为点上下标为 \(w[x]-dep[x]\) 的值。
我们设第一种情况开的桶为 \(up\),第二种情况开的桶为 \(down\),那么对于每个点 \(x\),其最终答案即为 \(up[w[x]+dep[x]]+down[w[x]-dep[x]]\)。
但问题是,我们在每个点上开两个桶是显然不可能的,而且就算空间能够支持每个点开两个桶,但我们在统计过程中,合并两个桶的时间复杂度也直接爆炸。
所以我们只能在全局开两个桶。
那么怎么维护子树信息呢?
不带修改的子树信息维护,正是dsu on tree大显身手的领域。
所以在每个点上开个 \(vector\),把每个点要维护的信息放进去,然后dsu on tree,需要统计信息的时候再把信息拿出来统计就好了。
每个点维护信息的时间均摊是 \(\frac{4m}{n}\) 的,每个点最多会被统计 \(O(logn)\) 次,一共有 \(n\) 个点,所以最后的时间复杂度是 \(O(mlogn)\) 的。
参考代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int N=3e5+10;
struct Edge{
int to,nex;
}edge[N<<1];int idx;
int h[N];
void add_edge(int u,int v){edge[++idx]={v,h[u]};h[u]=idx;}
int fa[N],dep[N],siz[N];
int son[N],top[N],w[N];
vector<int> info[N][4];
int up[N<<1],BUF[N<<2],*down=&BUF[N<<1];
int ans[N];
int n,m;
void dfs1(int p,int f)
{
fa[p]=f;
dep[p]=dep[f]+1;
siz[p]=1;
int max_son=0;
for(int i=h[p];~i;i=edge[i].nex)
{
int to=edge[i].to;
if(to==f)continue;
dfs1(to,p);
if(siz[to]>max_son)max_son=siz[to],son[p]=to;
siz[p]+=siz[to];
}
}
void dfs2(int p,int t)
{
top[p]=t;
if(!son[p])return;
dfs2(son[p],t);
for(int i=h[p];~i;i=edge[i].nex)
{
int to=edge[i].to;
if(to==fa[p]||to==son[p])continue;
dfs2(to,to);
}
}
inline int lca(int x,int y)
{
while(top[x]!=top[y])
{
if(dep[top[x]]<dep[top[y]])swap(x,y);
x=fa[top[x]];
}
return dep[x]<dep[y]?x:y;
}
void calc(int p,int flag,int val)
{
for(int i=0;i<info[p][0].size();i++)up[info[p][0][i]]+=val;
for(int i=0;i<info[p][1].size();i++)up[info[p][1][i]]-=val;
for(int i=0;i<info[p][2].size();i++)down[info[p][2][i]]+=val;
for(int i=0;i<info[p][3].size();i++)down[info[p][3][i]]-=val;
for(int i=h[p];~i;i=edge[i].nex)
{
int to=edge[i].to;
if(to==fa[p]||to==flag)continue;
calc(to,flag,val);
}
}
void dfs(int p,int keep)
{
for(int i=h[p];~i;i=edge[i].nex)
{
int to=edge[i].to;
if(to==fa[p]||to==son[p])continue;
dfs(to,0);
}
if(son[p])dfs(son[p],1);
calc(p,son[p],1);
ans[p]=up[dep[p]+w[p]]+down[w[p]-dep[p]];
if(!keep)calc(p,0,-1);
}
int main()
{
memset(h,-1,sizeof h);
scanf("%d%d",&n,&m);
for(int i=1,a,b;i<=n-1;i++)
{
scanf("%d%d",&a,&b);
add_edge(a,b);
add_edge(b,a);
}
for(int i=1;i<=n;i++)scanf("%d",&w[i]);
dfs1(1,0);
dfs2(1,1);
for(int i=1,s,t;i<=m;i++)
{
scanf("%d%d",&s,&t);
int z=lca(s,t);
info[s][0].push_back(dep[s]);
info[fa[z]][1].push_back(dep[s]);
info[t][2].push_back(dep[s]-2*dep[z]);
info[z][3].push_back(dep[s]-2*dep[z]);
}
dfs(1,1);
for(int i=1;i<=n;i++)printf("%d ",ans[i]);
return 0;
}