NOIP2016提高组 第一天第二题 天天爱跑步running 题解

55 篇文章 0 订阅
30 篇文章 0 订阅

题目描述

小c同学认为跑步非常有趣,于是决定制作一款叫做《天天爱跑步》的游戏。«天天爱跑步»是一个养成类游戏,需要玩家每天按时上线,完成打卡任务。

这个游戏的地图可以看作一一棵包含 个结点和 条边的树, 每条边连接两个结点,且任意两个结点存在一条路径互相可达。树上结点编号为从到的连续正整数。

现在有个玩家,第个玩家的起点为 ,终点为 。每天打卡任务开始时,所有玩家在第秒同时从自己的起点出发, 以每秒跑一条边的速度, 不间断地沿着最短路径向着自己的终点跑去, 跑到终点后该玩家就算完成了打卡任务。 (由于地图是一棵树, 所以每个人的路径是唯一的)

小C想知道游戏的活跃度, 所以在每个结点上都放置了一个观察员。 在结点的观察员会选择在第秒观察玩家, 一个玩家能被这个观察员观察到当且仅当该玩家在第秒也理到达了结点 。 小C想知道每个观察员会观察到多少人?

注意: 我们认为一个玩家到达自己的终点后该玩家就会结束游戏, 他不能等待一 段时间后再被观察员观察到。 即对于把结点作为终点的玩家: 若他在第秒重到达终点,则在结点的观察员不能观察到该玩家;若他正好在第秒到达终点,则在结点的观察员可以观察到这个玩家。

输入输出格式

输入格式:
第一行有两个整数和 。其中代表树的结点数量, 同时也是观察员的数量, 代表玩家的数量。

接下来 行每行两个整数和 ,表示结点 到结点 有一条边。

接下来一行 个整数,其中第个整数为 , 表示结点出现观察员的时间。

接下来 行,每行两个整数,和,表示一个玩家的起点和终点。

对于所有的数据,保证 。

输出格式:
输出1行 个整数,第个整数表示结点的观察员可以观察到多少人。

输入输出样例

输入样例#1:
6 3
2 3
1 2
1 4
4 5
4 6
0 2 5 1 2 3
1 5
1 3
2 6
输出样例#1:
2 0 0 1 1 1
输入样例#2:
5 3
1 2
2 3
2 4
1 5
0 1 0 3 0
3 1
1 4
5 5
输出样例#2:
1 2 1 0 1

题解

这题部分分很多,水分可以有80分
从链的那部分分入手
设f[i]表示第i个点作为出发点的数量
那么某条从x到y路径在f值中的对应位置就是f[x]
那么对于跑的路即x~y有两种可能,一种是往左,一种是往右
考虑往右的情况
记录一下有多少路径从每个点开始,在每个点结束的路径的起始点
那么在枚举点的时候,算答案之前把从这个点开始的路径在f值中的对应位置+1,算完答案后,把在这个点结束的路径在f值对应的位置-1
往左的情况相同,倒着搜就行了

对于一棵树,你想到了什么?
树链剖分!!
那么f[i]就是深度为i的点作为出发点的数量
那么某条从x到y路径在f值中的对应位置就是f[deep[x]]
然后把一条在树上的路径变成一堆路径,在分成的每个路径的开始和结束位置向上面一样打上标记
算答案的时候就不是枚举点,而是枚举dfs序,再把答案加在对应的点上
可以保证时间复杂度为 O(nlog2(m))
但是常数……
需要卡常

Code

#include<cstdio>
#include<cstring>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define N 301000
using namespace std;
int w[N],last[N*10],next[N*10],to[N*10],tot=0,n,m,dfn[N],fa[N],son[N],top[N],size[N],g[N+N+N],deep[N],ans[N];
int ls[N*10],nx[N*10],dt[N*10],to1=0,la[N*10],ne[N*10],da[N*10],to2=0,yl[N];
int ls2[N*10],nx2[N*10],dt2[N*10],to3=0,la2[N*10],ne2[N*10],da2[N*10],to4=0;
void putin(int x,int y)
{
    next[++tot]=last[x];last[x]=tot;to[tot]=y;
}
void put1(int x,int y)
{
    nx[++to1]=ls[x];ls[x]=to1;dt[to1]=y;
}
void put2(int x,int y)
{
    ne[++to2]=la[x];la[x]=to2;da[to2]=y;
}
void put3(int x,int y)
{
    nx2[++to3]=ls2[x];ls2[x]=to3;dt2[to3]=y;
}
void put4(int x,int y)
{
    ne2[++to4]=la2[x];la2[x]=to4;da2[to4]=y;
}
void dg1(int x)
{
    size[x]=1;int mx=0;
    for(int i=last[x];i;i=next[i])
    {
        if(size[to[i]]) continue;
        fa[to[i]]=x;deep[to[i]]=deep[x]+1;
        dg1(to[i]);size[x]+=size[to[i]];
        if(size[to[i]]>size[mx]) mx=to[i];
    }
    son[x]=mx;
}
void dg2(int x)
{
    dfn[x]=++tot;yl[tot]=x;if(son[x]) top[son[x]]=top[x],dg2(son[x]);
    for(int i=last[x];i;i=next[i])
    {
        if(dfn[to[i]]) continue;
        top[to[i]]=to[i];dg2(to[i]);
    }
}
int lca(int x,int y)
{
    int f1=top[x],f2=top[y];
    for(;f1!=f2;) if(deep[f1]>=deep[f2]) x=fa[f1],f1=top[x];else y=fa[f2],f2=top[y];
    if(deep[x]<deep[y]) return x;else return y;
}
void lct(int x,int y)
{
    int z=lca(x,y),bz=1;
    int jy1=deep[x],jy2=deep[z]-deep[x]+deep[z];
    int f1=top[x],f2=top[y];
    for(;f1!=f2;)
    {
        if(deep[f1]>=deep[f2]) put1(dfn[x],jy1),put2(dfn[f1],jy1),x=fa[f1];
        else put3(dfn[f2],jy2),put4(dfn[y],jy2),y=fa[f2];
        f1=top[x],f2=top[y];
    }
    if(deep[x]<deep[y]) put3(dfn[x],jy2),put4(dfn[y],jy2);
    else put1(dfn[x],jy1),put2(dfn[y],jy1);
}
int main()
{
    freopen("running.in","r",stdin);
    freopen("running.out","w",stdout);
    scanf("%d%d",&n,&m);int x,y;
    fo(i,1,n-1)
    {
        scanf("%d%d",&x,&y);
        putin(x,y);putin(y,x);
    }
    fo(i,1,n) scanf("%d",&w[i]);
    tot=0;deep[1]=1;top[1]=1;dg1(1);dg2(1);
    fo(i,1,m)
    {
        int x,y;scanf("%d%d",&x,&y);
        lct(x,y);
    }
    fo(i,1,n)
    {
        for(int k=ls2[i];k;k=nx2[k]) g[dt2[k]+N]++;
        ans[yl[i]]+=g[deep[yl[i]]-w[yl[i]]+N];
        for(int k=la2[i];k;k=ne2[k]) g[da2[k]+N]--;
    }
    fd(i,n,1)
    {
        for(int k=ls[i];k;k=nx[k]) g[dt[k]+N]++;
        ans[yl[i]]+=g[deep[yl[i]]+w[yl[i]]+N];
        for(int k=la[i];k;k=ne[k]) g[da[k]+N]--;
    }
    fo(i,1,n) printf("%d ",ans[i]);
    fclose(stdin);fclose(stdout);
    return 0;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值