NOIP2016 天天爱跑步

问题 G: 天天爱跑步

时间限制: 2 Sec   内存限制: 512 MB

题目描述

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

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

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

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

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

输入

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

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

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

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

对于所有的数据,保证1≤Si,Ti≤n,0≤ Wj ≤n。

输出

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

样例输入

6 3
2 3
1 2
1 4
4 5
4 6
0 2 5 1 2 3
1 5
1 3
2 6

样例输出

2 0 0 1 1 1


solution:
        用deep表示每个节点的深度,w表示每个观察员观察的时间。
        先用lca处理出每一个玩家起点和终点的最近公共祖先,然后用动态开点线段树去解决。
        我们可以把每一个玩家分为两步处理:向上走的和向下走的。向上走如果会被观察员观察到,deep[s]-deep[i]=w[i],再导一步deep[s]=deep[i]+w[i]。所以我们可以对于每一个玩家差分,在起点的dfs序上加1,lca的dfs序上减1,。每一层建一棵线段树,由dfs序卡出每一个节点的子树。这样,当我们全部插入后,只要查询第deep[i]+w[i]这一个深度的线段树中leftdfs[i]到rightdfs[i]区间内的权值,就可以知道从下向上走的ans[i]。
        从上向下走的比较难处理。对于向下跑的,如果玩家能被观察者i观察到,deep[s]+deep[i]-2*deep[lca[s]]=w[i].移相,得到deep[s]-2*deep[lca[s]]=w[i]-deep[i]。所以插入时在深度为deep[s]-2*deep[lca[s]]的线段树里进行差分,deep[t]处加1,deep[lca]处减1.查询时询问深度为w[i]-deep[i]线段树区间即可。
       注意,1、由于深度较大,节点多,所以使用动态开点线段树能有效节约内存。
                   2、向下走的w[i]-deep[i]可能为负数,所以应集体向右平移一个定值,数组也要相应开大一点。
                   3、做完向上跑的玩家之后,清空线段树。
附上代码:
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
struct tree{
    int u,v,next;
}l[601000];
struct tree2{
    int st,en,go;
}ll[301000];
int n,lian[301000],e,cnt,m,dep[301000],ldfn[301000],rdfn[301000];
int lc[7501000],rc[7501000],w[301000],p[300100][22],root[7501000];
int son[301000],size[301000],hh[7501000],fa[301000],an[300100],num;
void bian(int,int);
void dfs(int);
void work();
int lca(int,int);
void change(int,int,int,int,int&);
int search(int,int,int,int,int);
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<n;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        bian(x,y);
        bian(y,x);
    }
    for(int i=1;i<=n;i++)
        scanf("%d",&w[i]);
    dfs(1);
    work();
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&ll[i].st,&ll[i].en);
        ll[i].go=lca(ll[i].st,ll[i].en);
    }
    for(int i=1;i<=m;i++)
    {
        change(ldfn[ll[i].st],1,1,n,root[dep[ll[i].st]]);
        change(ldfn[fa[ll[i].go]],-1,1,n,root[dep[ll[i].st]]);
    }
    for(int i=1;i<=n;i++)
        an[i]+=search(ldfn[i],rdfn[i],1,n,root[dep[i]+w[i]]);
    memset(lc,0,sizeof(lc));
    memset(rc,0,sizeof(rc));
    memset(root,0,sizeof(root));
    cnt=0;
    memset(hh,0,sizeof(hh));
    for(int i=1;i<=m;i++)
    {
        change(ldfn[ll[i].en],1,1,n,root[dep[ll[i].st]-2*dep[ll[i].go]+2*n]);
        change(ldfn[ll[i].go],-1,1,n,root[dep[ll[i].st]-2*dep[ll[i].go]+2*n]);
    }
    for(int i=1;i<=n;i++)
        an[i]+=search(ldfn[i],rdfn[i],1,n,root[w[i]-dep[i]+2*n]);
    for(int i=1;i<=n;i++)
        printf("%d ",an[i]);
    return 0;
}
void bian(int x,int y)
{
    e++;
    l[e].u=x;
    l[e].v=y;
    l[e].next=lian[x];
    lian[x]=e;
}
void dfs(int x)
{
    ldfn[x]=++num;
    size[x]=1;
    for(int i=lian[x];i;i=l[i].next)
    {
        int v=l[i].v;
        if(v!=fa[x])
        {
            fa[v]=x;
            dep[v]=dep[x]+1;
            dfs(v);
            size[x]+=size[v];
            if(size[v]>size[son[x]])
                son[x]=v;
        }
    }
    rdfn[x]=num;
}
void work()
{
    for(int i=1;i<=n;i++)
        p[i][0]=fa[i];
    for(int i=1;i<=20;i++)
        for(int j=1;j<=n;j++)
            if(p[j][i-1]!=0)
                p[j][i]=p[p[j][i-1]][i-1];
}
int lca(int x,int y)
{
    if(dep[x]<dep[y])
        swap(x,y);
    int k=dep[x]-dep[y];
    for(int i=20;i>=0;i--)
        if(k-(1<<i)>=0)
        {
            k-=(1<<i);
            x=p[x][i];
        }
    if(x==y) return x;
    for(int i=20;i>=0;i--)
        if(p[x][i]!=0&&p[x][i]!=p[y][i])
        {
            x=p[x][i];
            y=p[y][i];
        }
    return fa[x];
}
void change(int x,int num,int le,int ri,int &now)
{
    if(x==0) return;
    if(now==0)
        now=++cnt;
    hh[now]+=num;
    if(le==ri) return;
    int mid=(le+ri)>>1;
    if(x<=mid)
        change(x,num,le,mid,lc[now]);
    else
        change(x,num,mid+1,ri,rc[now]);
}
int search(int ll,int rr,int le,int ri,int now)
{
    if(now==0)
        return 0;
    if(ll==le&&rr==ri)
        return hh[now];
    int mid=(le+ri)>>1;
    if(rr<=mid)
        return search(ll,rr,le,mid,lc[now]);
    else
        if(ll>mid)
            return search(ll,rr,mid+1,ri,rc[now]);
        else
            return search(ll,mid,le,mid,lc[now])+search(mid+1,rr,mid+1,ri,rc[now]);
}
动态开点线段树可以有很多打法,不一定像我一样打。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值