NOIp提高组 2016 天天爱跑步 题解

题目传送门

题目大意: 有一个数和若干个人,给出每个人的起点和终点,每个时刻,每个人会朝着终点走一步,走到终点后再过一个时刻这个人就没了。给出若干个询问,每次询问在时刻某位置处有多少人。

题解

先考虑一下特殊数据:树退化成链时

那么每一条路径在链上只有两种情况, 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[iw[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]);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值