(NOIP2016)天天爱跑步
真(™)是(NOIP)近几年中最难的一道……
先看一下部分分,有起点终点为根节点的分值,想到拆路径,对于路径((S,T)),拆为((S,LCA))和((T,LCA))。
首先明确一点,可能有多条路径有相同的起点,(LCA)或终点。
先考虑((S,LCA))这一半,一条路径只会对路径上的点产生贡献,不妨设有一个点(x)在这一半上。
则这条路径对(x)有贡献,当且仅当满足(d[u]=w[x]+d[x])时,发现对于一个点(观察员),(w[x]+d[x])为定值。
对于一个点(x),一条路径对其有贡献当且仅当这条路径的(LCA)为(x)或这条路径仅有一个端点在(x)的子树中(这样的路径(LCA)在(x)之上)
如图,蓝色和绿色的路径会产生贡献,而红色则不会。所以我们可以(Dfs)统计答案,即查看有多少个在(x)的子树中的路径起点的(d[u])等于(w[x]+d[x]),这样统计出的答案只会多不会少,我们考虑如何去掉多余的答案。
先看一下实现过程,先开一个(Bag[])记录像(d[u])这样的值,在(x)的子树中处理完之后只要提出(Bag[w[x]+d[x]])的答案即可,但这样我们会发现会统计进起点根本不是(x)子树中的路径,因此我们先记录下到(x)时(Bag[w[x]+d[x]])的值,在处理完子树之后再将(Bag[w[x]+d[x]])的值和原值相减即可。
但是还有像红色这样的路径会被考虑,所以在跑(x)的子树中的节点(设为(y))时,我们将以(y)为(LCA)的路径从桶中减去即可,这样我们统计到的答案就没有多余的了。
同样,考虑((T,LCA))时,我们要满足的式子为:(w[x]-d[x]=d[u]-2*d[LCA])即可。(注意这里可能出现负数,我们加上(N_{max}))即可。
还有一点,在计算(x)的答案时,不要忘了以(x)为路径起点或终点时可以对上面产生贡献,要同时更新(Bac)中的值。
做完之后,所有路径(LCA)的点答案会被算两次,要减掉
#include<bits/stdc++.h>
using namespace std;
#define int long long
inline int read()
{
int f=1,w=0;char x=0;
while(x<'0'||x>'9') {if(x=='-') f=-1; x=getchar();}
while(x!=EOF&&x>='0'&&x<='9') {w=(w<<3)+(w<<1)+(x^48);x=getchar();}
return w*f;
}
const int N=300010,M=1000010;
int n,m,num_edge,MD;
int las[N][21],ans[N],boki[N];
vector<int> Lex[N],End[N],LCA[N];
int head[N],Dep[N],W[N],Bag[M];
struct Edge{int next,to;} edge[N<<1];
struct Group{int from,to,lca;} Ply[N];
inline void Add(int from,int to)
{
edge[++num_edge].next=head[from];
edge[num_edge].to=to;
head[from]=num_edge;
}
inline void Dfs(int pos,int fa)
{
las[pos][0]=fa;Dep[pos]=Dep[fa]+1;
for(int i=0;i<20;i++) las[pos][i+1]=las[las[pos][i]][i];
for(int i=head[pos];i;i=edge[i].next)
if(edge[i].to!=fa) Dfs(edge[i].to,pos);
}
inline int LCA_Ask(int u,int v)
{
if(Dep[u]<Dep[v]) swap(u,v);
for(int i=20;i>=0;i--) if(Dep[v]<=Dep[u]-(1<<i)) u=las[u][i];
if(u==v) return u;
for(int i=20;i>=0;i--) if(las[u][i]!=las[v][i]) u=las[u][i],v=las[v][i];
return las[u][0];
}
inline void Dfs_For_From(int pos,int fa)//统计路径(S,LCA)的答案
{
int Num=Dep[pos]+W[pos],Now;if(Num<=MD) Now=Bag[Num];
for(int i=head[pos];i;i=edge[i].next)
if(edge[i].to!=fa) Dfs_For_From(edge[i].to,pos);
Bag[Dep[pos]]+=boki[pos];//以pos为起点的路径对上面的点产生的贡献
if(Num<=MD) ans[pos]=Bag[Num]-Now;
for(int i=0;i<(int)Lex[pos].size();i++) Bag[Dep[Lex[pos][i]]]--;//以pos为LCA的路径对上面的点不会在产生贡献了
}
inline void Dfs_For_To(int pos,int fa)//统计路径(LCA,T)的答案
{
int Num=Dep[pos]-W[pos]+N,Now;Now=Bag[Num];
for(int i=head[pos];i;i=edge[i].next)
if(edge[i].to!=fa) Dfs_For_To(edge[i].to,pos);
for(int i=0;i<(int)End[pos].size();i++) Bag[N+End[pos][i]]++;//以pos为终点的路径对上面的点产生的贡献
ans[pos]+=Bag[Num]-Now;
for(int i=0;i<(int)LCA[pos].size();i++) Bag[N+LCA[pos][i]]--;//以pos为LCA的路径对上面的点不会在产生贡献了
}
main(){
#ifndef ONLINE_JUDGE
freopen("A.in","r",stdin);//Ans=2 0 0 1 1
//freopen("B.in","r",stdin);//Ans=1 2 1 0 1
freopen("A.out","w",stdout);
#endif
n=read(),m=read();MD=-1;
for(int i=1,u,v;i<n;i++)
u=read(),v=read(),Add(u,v),Add(v,u);
for(int i=1;i<=n;i++) W[i]=read();Dfs(1,0);
for(int i=1;i<=n;i++) MD=max(MD,Dep[i]);
for(int i=1;i<=m;i++)
{
Ply[i].from=read(),Ply[i].to=read();boki[Ply[i].from]++;
Ply[i].lca=LCA_Ask(Ply[i].from,Ply[i].to);
Lex[Ply[i].lca].push_back(Ply[i].from);
End[Ply[i].to].push_back(2*Dep[Ply[i].lca]-Dep[Ply[i].from]);
LCA[Ply[i].lca].push_back(2*Dep[Ply[i].lca]-Dep[Ply[i].from]);
}
Dfs_For_From(1,0);memset(Bag,0,sizeof(Bag));Dfs_For_To(1,0);
for(int i=1;i<=m;i++)
if(Dep[Ply[i].from]-Dep[Ply[i].lca]==W[Ply[i].lca])
ans[Ply[i].lca]--;//减去路径LCA答案重复计算的部分
for(int i=1;i<=n;i++) printf("%lld ",ans[i]);
}