题目大意: 有一个数和若干个人,给出每个人的起点和终点,每个时刻,每个人会朝着终点走一步,走到终点后再过一个时刻这个人就没了。给出若干个询问,每次询问在时刻某位置处有多少人。
题解
先考虑一下特殊数据:树退化成链时
那么每一条路径在链上只有两种情况, d e e p [ S ] ≤ d e e p [ T ] deep[S] \leq deep[T] deep[S]≤deep[T] o r or or d e e p [ S ] ≥ d e e p [ T ] deep[S] \geq deep[T] deep[S]≥deep[T],对于 d e e p [ S ] ≤ d e e p [ T ] deep[S] \leq deep[T] deep[S]≤deep[T] 的情况,可以发现,它如果要对节点 i i i 产生贡献,那么一定满足 d e e p [ i ] − w [ i ] = d e e p [ S ] deep[i]-w[i]=deep[S] deep[i]−w[i]=deep[S] 并且 d e e p [ T ] ≥ d e e p [ i ] deep[T] \geq deep[i] deep[T]≥deep[i],那么我们就可以开一个数组 h h h,记录当前在 i i i 节点有多少个起点,对于每一个 i i i, a n s [ i ] = h [ i − w [ i ] ] ans[i]=h[i-w[i]] ans[i]=h[i−w[i]],当走到一个终点T的时候,对应的 h [ S ] − 1 h[S]-1 h[S]−1 即可。
对于另一种情况,基本同上,要反着循环(为了先遍历到每条路径的起点),并且 a n s [ i ] = h [ i + w [ i ] ] ans[i]=h[i+w[i]] ans[i]=h[i+w[i]]
那这跟正解有什么关系吗?
当然有!树不就是由一堆链组成的吗!
对于每一条路径 S S S ~ T T T,我们将它拆成 S S S ~ l c a ( S , T ) lca(S,T) lca(S,T) 和 l c a ( S , T ) lca(S,T) lca(S,T) ~ T T T 两个部分,对于这两个部分,我们就可以用类似上面的办法统计答案了。
具体实现方法:
第一部分
假如一条路径可以对节点i产生贡献,那么一定满足 d e e p [ S ] − w [ i ] = d e e p [ i ] deep[S]-w[i]=deep[i] deep[S]−w[i]=deep[i],移项得 d e e p [ S ] = d e e p [ i ] + w [ i ] deep[S]=deep[i]+w[i] deep[S]=deep[i]+w[i],也就是说,对于每一个节点 i i i,对它有贡献的路径的起点深度一定是 d e e p [ i ] + w [ i ] deep[i]+w[i] deep[i]+w[i],并且要在它的子树内。
那么同样搞一个数组 h h h,像上面一样搞即可。但是有个细节,对于你用来统计答案的 h [ d e e p [ i ] + w [ i ] ] h[deep[i]+w[i]] h[deep[i]+w[i]],只有在你子树内更新的部分才有用,所以要在进来的时候记录一下,回溯的时候与进来时记下的那个相减,就能得到子树内的深度为 d e e p [ i ] + w [ i ] deep[i]+w[i] deep[i]+w[i] 的起点数。
第二部分
对于这条部分,我们发现 l c a ( S , T ) lca(S,T) lca(S,T) 是在 T T T 的上面的,那就很尴尬,不大能沿用上面的方法啊。
我们设
l
e
n
len
len 为
S
S
S 到
T
T
T 路径的长度,那么类似上面的式子,可以得到
d
e
e
p
[
T
]
−
l
e
n
=
d
e
e
p
[
i
]
−
w
[
i
]
deep[T]-len=deep[i]-w[i]
deep[T]−len=deep[i]−w[i]
怎么理解这条式子呢?
我们假设把所有的路径的第一部分往上翻,就像这样:
现在把第一部分向上翻一下
回到上面,看那个等式左边部分,
d
e
e
p
[
T
]
−
l
e
n
deep[T]-len
deep[T]−len,正好就是
S
′
S'
S′的深度,再看右边部分,
d
e
e
p
[
i
]
−
w
[
i
]
deep[i]-w[i]
deep[i]−w[i],因为这是个等式,所以它也表示
S
′
S'
S′ 的深度,那么假如
d
e
e
p
[
i
]
−
w
[
i
]
=
d
e
e
p
[
S
′
]
deep[i]-w[i]=deep[S']
deep[i]−w[i]=deep[S′],说明
S
′
S'
S′ 在向下走
w
[
i
]
w[i]
w[i] 步后刚好到
i
i
i 点,那么此时
S
′
S'
S′ 就能对
i
i
i 点产生贡献。
综上,如果一条路径与一个节点i满足式子 d e e p [ T ] − l e n = d e e p [ i ] − w [ i ] deep[T]-len=deep[i]-w[i] deep[T]−len=deep[i]−w[i],那么这条路径就可以对这个节点产生贡献。
得到这条式子之后,就可以像上面一样乱搞了。
但是还有个问题,我们知道 S ′ S' S′ 是由 S S S 向上翻转得到的,那么这意味着, S ′ S' S′ 的深度很可能是负数,所以,我们还要给加一个偏移量 300000 300000 300000,即总结点数。
剩下的就是代码了:
#include <cstdio>
#include <cstring>
#define maxn 600010
int n,m,len=0;
struct node{int x,y,next;};
node e[2*maxn],st[maxn],ed[maxn];
int first[maxn],fir[maxn],ffir[maxn];
int f[maxn][20];
int deep[maxn];
int w[maxn];
void buildroad(node *ak,int *fi,int x,int y)
{
len++;
ak[len].x=x;
ak[len].y=y;
ak[len].next=fi[x];
fi[x]=len;
}
void dfs_getfa(int x,int fa)//预处理倍增的f数组以及每个点的深度
{
for(int i=first[x];i;i=e[i].next)
{
int y=e[i].y;
if(y==f[x][0])continue;
deep[y]=deep[x]+1;
f[y][0]=x;
dfs_getfa(y,x);
}
}
void swap(int &x,int &y){int tt=x;x=y;y=tt;}
int lca(int x,int y)
{
if(deep[x]!=deep[y])
{
if(deep[x]>deep[y])swap(x,y);
for(int i=18;i>=0;i--)
if(deep[f[y][i]]>=deep[x])y=f[y][i];
}
for(int i=18;i>=0;i--)
if(f[x][i]!=f[y][i])x=f[x][i],y=f[y][i];
return x==y?x:f[x][0];
}
struct way{int s,t,len,LCA;};
way s[maxn];//记录路径
int ans[maxn],h[2*maxn],sta[maxn];//sta[i]表示以i点为起点的路径数
void dfs_1(int x)
{
int now=h[deep[x]+w[x]];
for(int i=first[x];i;i=e[i].next)
{
int y=e[i].y;
if(f[x][0]==y)continue;
dfs_1(y);
}
h[deep[x]]+=sta[x];//记录下以x点作为起点的路径数
ans[x]+=h[deep[x]+w[x]]-now;//记录答案
for(int i=fir[x];i;i=ed[i].next)//去掉以x点为第一部分的终点的路径,因为他不会对上面的点产生贡献
h[deep[ed[i].y]]--;
}
void dfs_2(int x)
{
int now=h[deep[x]-w[x]+300000];
for(int i=first[x];i;i=e[i].next)
{
int y=e[i].y;
if(f[x][0]==y)continue;
dfs_2(y);
}
for(int i=fir[x];i;i=ed[i].next)//记录下以x点为终点的路径数
h[ed[i].y+300000]++;
ans[x]+=h[deep[x]-w[x]+300000]-now;
for(int i=ffir[x];i;i=st[i].next)//去掉以x为第二部分起点的路径
h[st[i].y+300000]--;
}
int main()
{
scanf("%d %d",&n,&m);
for(int i=1;i<n;i++)
{
int x,y;
scanf("%d %d",&x,&y);
buildroad(e,first,x,y);
buildroad(e,first,y,x);
}
deep[1]=1;
dfs_getfa(1,0);
for(int i=1;i<=n;i++)
scanf("%d",&w[i]);
for(int j=1;j<=18;j++)
for(int i=1;i<=n;i++)
f[i][j]=f[f[i][j-1]][j-1];
for(int i=1;i<=m;i++)
{
scanf("%d %d",&s[i].s,&s[i].t);
s[i].LCA=lca(s[i].s,s[i].t);
s[i].len=deep[s[i].s]+deep[s[i].t]-deep[s[i].LCA]*2;
}
len=0;
for(int i=1;i<=m;i++)//这里使用邻接表来记录以某个点为终点的路径的起点
buildroad(ed,fir,s[i].LCA,s[i].s),sta[s[i].s]++;
dfs_1(1);
len=0;
memset(fir,0,sizeof(fir));
for(int i=1;i<=m;i++)
buildroad(ed,fir,s[i].t,deep[s[i].t]-s[i].len),buildroad(st,ffir,s[i].LCA,deep[s[i].t]-s[i].len);
dfs_2(1);
for(int i=1;i<=m;i++)
//去重,因为每条路径的两个部分中都包含了它的lca
//所以如果这条路径的起点可以对它的lca这个点产生贡献的话,这个贡献会被计算两次
if(deep[s[i].s]-w[s[i].LCA]==deep[s[i].LCA])ans[s[i].LCA]--;
for(int i=1;i<=n;i++)
printf("%d ",ans[i]);
}