Luogu P1600 NOIP2016提高组 天天爱跑步 题解 [ 紫 ] [ 线段树合并 ] [ 树上差分 ]
天天爱跑步:这题主要难点还是想到根据 lca 分成两边,分讨算贡献。
思路
首先看到树上路径,很容易就能想到树上差分计算每个跑步的人的贡献。
那么一个人的贡献改如何计算呢?我们考虑某个观察员他在
因此,我们就可以根据路径上每个点到起点的距离来计算贡献。
而由于起点和终点的 lca 两边的点的距离计算方式不相同,所以需要进行分类讨论:
当路径未经过 lca 时
这时候观察员
当路径经过 lca 时
这时候观察员
于是,我们就可以开桶记录下每个值对应的贡献,查询的时候答案就是
这个桶我们可以用动态开点线段树实现,然后做树上前缀和求答案的时候直接线段树合并即可。
注意这个线段树会存负数下标,且树上差分的时候要先在 lca 处减掉未经过 lca 的贡献,在 lca 的父亲处再减掉经过 lca 的贡献,不然会导致未经过 lca 的贡献被减两次或者经过 lca 的贡献被减两次。
时间复杂度
代码
#include <bits/stdc++.h>
#define fi first
#define se second
#define eb(x) emplace_back(x)
#define pb(x) push_back(x)
#define lc(x) (tr[x].ls)
#define rc(x) (tr[x].rs)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ldb;
using pi=pair<int,int>;
const int N=300005;
int n,m,w[N],fa[N],top[N],dep[N],sz[N],son[N],ans[N];
vector<int>g[N];
struct Node{
int ls,rs,v;
};
struct Segtree{
Node tr[80*N];
int root[N],tot=0;
void update(int &u,int ln,int rn,int x,int k)
{
if(u==0)u=++tot;
tr[u].v+=k;
if(ln==rn)return;
int mid=(ln+rn)>>1;
if(x<=mid)update(lc(u),ln,mid,x,k);
else update(rc(u),mid+1,rn,x,k);
}
int merge(int x,int y)
{
if(x==0||y==0)return x+y;
tr[x].v+=tr[y].v;
tr[x].ls=merge(lc(x),lc(y));
tr[x].rs=merge(rc(x),rc(y));
return x;
}
int query(int u,int ln,int rn,int x)
{
if(ln==rn)return tr[u].v;
int mid=(ln+rn)>>1;
if(x<=mid)return query(lc(u),ln,mid,x);
else return query(rc(u),mid+1,rn,x);
}
}tr1;
void dfs1(int u,int f)
{
dep[u]=dep[f]+1;fa[u]=f;sz[u]=1;
for(auto v:g[u])
{
if(v==f)continue;
dfs1(v,u);
sz[u]+=sz[v];
if(sz[son[u]]<sz[v])son[u]=v;
}
}
void dfs2(int u,int tp)
{
top[u]=tp;
if(son[u]==0)return;
dfs2(son[u],tp);
for(auto v:g[u])
{
if(v==fa[u]||v==son[u])continue;
dfs2(v,v);
}
}
int getlca(int u,int v)
{
while(top[u]!=top[v])
{
if(dep[top[u]]<dep[top[v]])swap(u,v);
u=fa[top[u]];
}
if(dep[u]<dep[v])swap(u,v);
return v;
}
void dfs3(int u,int f)
{
for(auto v:g[u])
{
if(v==f)continue;
dfs3(v,u);
tr1.root[u]=tr1.merge(tr1.root[u],tr1.root[v]);
}
ans[u]=tr1.query(tr1.root[u],-N,N,dep[u]+w[u]);
if(w[u])ans[u]+=tr1.query(tr1.root[u],-N,N,dep[u]-w[u]);
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin>>n>>m;
for(int i=1;i<n;i++)
{
int u,v;
cin>>u>>v;
g[u].push_back(v);
g[v].push_back(u);
}
dfs1(1,0);
dfs2(1,1);
for(int i=1;i<=n;i++)cin>>w[i];
while(m--)
{
int s,t;
cin>>s>>t;
int lca=getlca(s,t);
int flca=fa[lca];
tr1.update(tr1.root[s],-N,N,dep[s],1);
tr1.update(tr1.root[t],-N,N,2*dep[lca]-dep[s],1);
tr1.update(tr1.root[lca],-N,N,dep[s],-1);
tr1.update(tr1.root[flca],-N,N,2*dep[lca]-dep[s],-1);
}
dfs3(1,0);
for(int i=1;i<=n;i++)cout<<ans[i]<<" ";
return 0;
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 推荐几款开源且免费的 .NET MAUI 组件库
· 实操Deepseek接入个人知识库
· 易语言 —— 开山篇
· Trae初体验