这道题。有点丧心病狂。。为什么会出现在NOIP里呢。。。先讲部分分:
对于前25分 瞎暴力即可。
对于Si=1的20分:
我们发现Si=1时,对于在点i的观察员。
深度为dep[i],出现时间为w[i]。因为所有的点都是由上到下的,所以只有S在上面时才会对点i贡献。
会对i造成贡献的点S: dep[i]-w[i]=dep[S]。而所有的点S都为1。
得到恒等式:dep[i]-w[i]=1。这样的点答案才不会为0.
所以可以树剖+线段树维护一下每个点经过的次数,然后对于dep[i]-w[i]!=1输出0,否则输出线段树上 那个点的值。
对于Ti=1的20分:
所有的点都向上走。我们思考点怎么获得贡献。
向上走 点i若获得贡献:dep[i]+w[i]=dep[s]。起点S的dep满足这个条件到那个点即可有贡献。
所以开一个数组记录从X出发的点现在还没结束的有多少。(桶的思想)。
记录下以x为起点的有多少个,dfs一遍,当这个起点退栈时说明现在这个路径在dfs出栈的过程了。而终点都是1,所以不会出栈,就回溯时统计下子树中点到这里的个数就好了。
那么如何统计子树呢?根据dfs序。到达时记录tmp,回溯时的值相减就是子树的贡献。
对于一条链的15分:
线性的一个桶,从左往右扫。vector存关系。
记录下当前的起点有多少条线段。线性扫的时候,每个点进行三个操作存入桶中,计算,出桶。
从右向左同理。
对于100%的得分:
我们知道一条路上树的路径有且只有一条。
综上所述:我们把路径S->T拆成从S->LCA(S,T)和LCA(S,T)->T。
一条向上的路径和一条向下的路径。
我们对于每个观测点进行统计。
对于向上的路径非常简单,一个点i贡献当且仅当dep【i】+w【i】=dep【s】时才有贡献。
记录每个点出发的路径条数,和用vector记录到哪个点以s开始的路径中断。
dfs时注意判断越界的情况不合法,不能计算(重要)。
到一个点的时候同样先记录tmp——dep深度的起点有多少个。
回溯时更新该深度起点的值和该点的答案。注意是深度!最后该点出栈时,把桶里的东西拿出来(深度!)。
从上往下就有点麻烦。。当LCA是S时。dep【i】-w【i】=dep【s】时有贡献,然而。如果起点是拐弯过来的呢?发现dep【i】-w【i】的值得到的是负的拐弯深度。有点难思考,w【i】的实质其实就是他们的距离。所以要找到起点,我们可以考虑这是一个边权为1的树。dis(s,t)不就是 s到lca和lca到t吗。
dep【i】-w【i】如果是起点层的深度,那么一定有dep【t】-dis(s,t)即负的s到lca=dep【i】-w【i】。
所以我们记录lca和t的起点层深度都是dep【t】-dis(s,t)。
终点下面的点肯定没有影响,LCA之上的点也没有影响。让LCA到终点之间的点统计这段距离,那么就在终点出栈时放入桶中,起点出栈时拿出桶中。
注意设置一个点数MAXN的偏移量防止负数。
#include<bits/stdc++.h>
using namespace std;
const int MAXN=3e5+5;
struct edge{
int to,next,w;
}e[MAXN<<1];
struct data{
int u,v,lca,dis;
}nd[MAXN];
int n,m,w[MAXN];
int head[MAXN],cnt=0;
inline void add(int u,int v){
e[++cnt]=(edge){v,head[u],1},head[u]=cnt;
e[++cnt]=(edge){u,head[v],1},head[v]=cnt;
}
int fa[MAXN],dep[MAXN],size[MAXN],hson[MAXN],dis[MAXN],DEP=0;
void dfs1(int u,int father){
size[u]=1;
dep[u]=dep[father]+1;
fa[u]=father;
for(int i=head[u];i;i=e[i].next){
int v=e[i].to,w=e[i].w;
if(v==father)continue;
dis[v]=dis[u]+w;
dfs1(v,u);
size[u]+=size[v];
if(size[v]>size[hson[u]]||!hson[u])hson[u]=v;
}
}
int top[MAXN];
void dfs2(int u,int tp){
top[u]=tp;
if(hson[u])dfs2(hson[u],tp);
for(int i=head[u];i;i=e[i].next){
int v=e[i].to;
if(v==fa[u]||v==hson[u])continue;
dfs2(v,v);
}
}
inline int lca(int x,int y){
int tx=top[x],ty=top[y];
while(tx!=ty){
if(dep[tx]>dep[ty])
x=fa[tx];
else
y=fa[ty];
tx=top[x],ty=top[y];
}
if(dep[x]<dep[y])swap(x,y);
return y;
}
vector<int>v1[MAXN],v2[MAXN<<1],v3[MAXN<<1];
int val[MAXN];
int ans[MAXN],A[MAXN],B[MAXN<<1];
//往上走的部分。
void getans1(int u,int fa){
int tmp;
if(dep[u]+w[u]<=DEP)tmp=A[dep[u]+w[u]];//记录下面到他的点现在是多少
for(int i=head[u];i;i=e[i].next){
int v=e[i].to;
if(v==fa)continue;
getans1(v,u);
}
A[dep[u]]+=val[u];//给这一层加上路径 入栈
if(dep[u]+w[u]<=DEP)ans[u]+=A[dep[u]+w[u]]-tmp;//子树里的答案 退栈时子树一定被遍历了
for(int i=0;i<v1[u].size();i++){
A[dep[v1[u][i]]]--;//出栈时 到这里结束的点(那个深度)退掉 避免影响父亲中还在栈里的点
}
}
//往下走的部分 我们只关心这个点i 有没有从他对应的起点可以到达i的点。
//所以记录下起点 终点来入栈出栈。
//注意,getans1中因为是从下到上,所以是起点在下面 所以是起点入栈开始记录
//同样记录差值。
void getans2(int u,int fa){
int tmp=B[dep[u]-w[u]+300000];
for(int i=head[u];i;i=e[i].next){
int v=e[i].to;
if(v==fa)continue;
getans2(v,u);
}
for(int i=0;i<v3[u].size();i++){//终点出栈 记录
B[v3[u][i]+300000]++;//本身这就是一层里的起点了 不用dep
}
ans[u]+=B[dep[u]-w[u]+300000]-tmp;
for(int i=0;i<v2[u].size();i++){//起点出栈 删掉
B[v2[u][i]+300000]--;
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<n;i++){
int u,v;
scanf("%d%d",&u,&v);
add(u,v);
}
for(int i=1;i<=n;i++){
scanf("%d",&w[i]);
}
dfs1(1,1);
dfs2(1,1);
for(int i=1;i<=n;i++)DEP=max(dep[i],DEP);
for(int i=1;i<=m;i++){
scanf("%d%d",&nd[i].u,&nd[i].v);
nd[i].lca=lca(nd[i].u,nd[i].v);
nd[i].dis=dep[nd[i].u]+dep[nd[i].v]-(dep[nd[i].lca]<<1);
v1[nd[i].lca].push_back(nd[i].u);//记录向上路径出栈顺序
val[nd[i].u]++;//记录向上路径的条数
}
getans1(1,0);
for(int i=1;i<=m;i++){
v2[nd[i].lca].push_back(dep[nd[i].v]-nd[i].dis);//记录起点
v3[nd[i].v].push_back(dep[nd[i].v]-nd[i].dis);//记录终点
//当且仅当 dep[i]-w[i]=起点(dep[t]-dis(s,t)
}
getans2(1,0);
for(int i=1;i<=m;i++){//向上 dep[i]+w[i]=dep[s]
if(dep[nd[i].lca]+w[nd[i].lca]==dep[nd[i].u])ans[nd[i].lca]--;
}
for(int i=1;i<=n;i++)printf("%d ",ans[i]);
return 0;
}
/*
4 1
1 2
2 4
1 3
2 1 0 1
4 1
*/