Luogu P1600 [NOIP2016 提高组] 天天爱跑步
Solution
我们可以将玩家 \(i\) 跑步的路线分成两段:\(s_i\rightarrow\mathrm{LCA}(s_i,t_i)\) 和 \(\mathrm{LCA}(s_i,t_i)\rightarrow t_i\)。
设 \(\mathrm{dep}_i\) 表示树上 \(i\) 号节点的深度,\(\mathrm{fa}_i\) 表示树上 \(i\) 号节点的父节点。
若节点 \(x\) 可以观察到玩家 \(i\) 的行动,当且仅当:
-
节点 \(x\) 在 \(s_i\rightarrow\mathrm{LCA}(s_i,t_i)\) 的路径上,且有 \(\mathrm{dep}_{s_i}-\mathrm{dep}_x=w_x\)
-
节点 \(x\) 在 \(\mathrm{LCA}(s_i,t_i)\rightarrow t_i\) 的路径上,且有 \(\mathrm{dep}_{s_i}+\mathrm{dep}_x-2 \times \mathrm{dep}_{\mathrm{LCA}(s_i,t_i)}=w_x\)
两者互不干涉,于是分成两类讨论,答案相加即可。
如果节点 \(x\) 在 \(s_i\rightarrow\mathrm{LCA}(s_i,t_i)\) 的路径上,那么移项得 \(\mathrm{dep}_{s_i}=\mathrm{dep}_x+w_x\)。这样做相当于在 \(s_i\rightarrow\mathrm{LCA}(s_i,t_i)\) 的路径上新增一个物品 \(\mathrm{dep}_{s_i}\)。询问每一个点 \(x\) 上的物品 \(\mathrm{dep}_x+w_x\) 的数量。通过树上差分,我们可以将其视作:该物品在 \(s_i\) 处出现,并在 \(\mathrm{fa}_{\mathrm{LCA}(s_i,t_i)}\) 处消失。我们称其为第一种情况。
如果节点 \(x\) 在 \(\mathrm{LCA}(s_i,t_i)\rightarrow t_i\) 的路径上,那么移项得 \(\mathrm{dep}_{s_i}-2 \times \mathrm{dep}_{\mathrm{LCA}(s_i,t_i)}=w_x-\mathrm{dep}_x\)。这样做相当于在 \(\mathrm{LCA}(s_i,t_i)\rightarrow t_i\) 的路径上新增一个物品 \(\mathrm{dep}_{s_i}-2 \times \mathrm{dep}_{\mathrm{LCA}(s_i,t_i)}\)。询问每一个点 \(x\) 上的物品 \(w_x-\mathrm{dep}_x\) 的数量。通过树上差分,我们可以将其视作:该物品在 \(t_i\) 处出现,并在 \(\mathrm{LCA}(s_i,t_i)\) 处消失。我们称其为第二种情况。
对于每一次询问,我们存下每一个物品出现和消失的位置。与此同时,记录一个桶 \(\mathrm{cnt}_{0/1,i}\) 表示第一种情况和第二种情况下的类型 \(i\) 物品的数量。
考虑 DFS 遍历整棵树。递归到一个节点 \(x\),先存下目前的 \(\mathrm{cnt}_{0,\mathrm{dep}_x+w_x}\) 和 \(\mathrm{cnt}_{1,w_x-\mathrm{dep}_x}\),然后查看 \(x\) 位置出现或消失的物品并更新 \(\mathrm{cnt}_{0/1}\),继续递归节点 \(x\) 的子节点。
递归完后,现在的 \(\mathrm{cnt}_{0,\mathrm{dep}_x+w_x}\) 和之前的 \(\mathrm{cnt}_{0,\mathrm{dep}_x+w_x}\) 的差值, 现在的 \(\mathrm{cnt}_{1,w_x-\mathrm{dep}_x}\) 和之前的 \(\mathrm{cnt}_{1,w_x-\mathrm{dep}_x}\) 的差值就是两种情况分别的答案。
Code
/* ChongYun */
#include<bits/stdc++.h>
#define int long long
#define fir first
#define sec second
using namespace std;
int read(){
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=(x<<1)+(x<<3)+ch-'0';
ch=getchar();
}
return x*f;
}
int mov(int x){ return x+300001; }
int n,m,w[300005];
int s[300005],t[300005];
struct Edge{
int to,nxt,val;
}e[300005<<1];
int hd[300005],ecnt=0;
void Link(int x,int y){
++ecnt;
e[ecnt].to=y;
e[ecnt].nxt=hd[x];
hd[x]=ecnt;
return ;
}
int fath[300005];
int dep[300005],dp[300005][21];
void initdfs(int x,int fa){
fath[x]=fa; dep[x]=dep[fa]+1; dp[x][0]=fa;
for(int i=1;(1<<i)<=dep[x];i++) dp[x][i]=dp[dp[x][i-1]][i-1];
for(int i=hd[x];i!=0;i=e[i].nxt){
int y=e[i].to;
if(y==fa) continue;
initdfs(y,x);
}
return ;
}
int LCA(int x,int y){
if(dep[y]<dep[x]) swap(x,y);
for(int i=20;i>=0;i--){
if(dep[dp[y][i]]>=dep[x]) y=dp[y][i];
}
if(x==y) return x;
for(int i=20;i>=0;i--){
if(dp[x][i]!=dp[y][i]){
x=dp[x][i];
y=dp[y][i];
}
}
return dp[x][0];
}
int cnt[2][300005<<1];
vector<pair<int,int> > add[300005],del[300005];
int ans[300005];
void dfs(int x,int fa){
int now0=cnt[0][dep[x]+w[x]];
int now1=cnt[1][mov(w[x]-dep[x])];
for(auto now:add[x]) ++cnt[now.fir][now.sec];
for(auto now:del[x]) --cnt[now.fir][now.sec];
for(int i=hd[x];i!=0;i=e[i].nxt){
int y=e[i].to;
if(y==fa) continue;
dfs(y,x);
}
ans[x]=cnt[0][dep[x]+w[x]]-now0+cnt[1][mov(w[x]-dep[x])]-now1;
return ;
}
signed main(){
n=read(); m=read();
for(int i=1;i<n;i++){
int u=read(),v=read();
Link(u,v);
Link(v,u);
}
initdfs(1,0);
for(int i=1;i<=n;i++) w[i]=read();
for(int i=1;i<=m;i++){
s[i]=read(); t[i]=read();
int qwq=LCA(s[i],t[i]);
add[s[i]].push_back({0,dep[s[i]]});
del[fath[qwq]].push_back({0,dep[s[i]]});
add[t[i]].push_back({1,mov(dep[s[i]]-2*dep[qwq])});
del[qwq].push_back({1,mov(dep[s[i]]-2*dep[qwq])});
}
dfs(1,0);
for(int i=1;i<=n;i++) printf("%lld ",ans[i]);
printf("\n");
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!