LCA 天天爱跑步

问题 G: 天天爱跑步
时间限制: 2 Sec 内存限制: 512 MB
提交: 24 解决: 20
[提交][状态][讨论版]
题目描述
小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

很显然要用到LCA,但正解比LCA多很多。
一条路该分成两段,起点x到LCA,LCA到终点y。这里就要分别考虑了。对于第一段,对于其路径上的点i,t[i]==dep[x]-dep[i]时,就是dep[x]==t[i]+dep[i]。所以对于起点,就看对于点i有多少人起步于t[i]+dep[i]就好了。 那么对于第二段,len-(dep[y]-dep[i])==t[i],就是t[i]-dep[i]==len-depy,以同样的方式统计就行了。
这只是主体思路,离实现还差很远。因为同一深度不同路径会对答案产生影响,所以可以再来一发深搜。这样判断就变得较容易了。 可以开两个桶记录先前所述的第一段和第二段对答案的贡献,但之前的会产生影响,可以这样考虑,记录搜自己和子节点之前的值,用搜完的之后的值减去他,就是自己子树要过自己的数量了。。。而为了消除LCA对答案的影响,回溯回来时,要分别把以他为LCA的起点和终点的点在两个桶中分别减减
在这里搜答案时,我学(zhao)习(chao)神犇ljm的做法,把每个点向他所能对应的答案练了一条边。方便在dfs时查找答案,或是在回溯时减去。。

#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<iostream>
#define N 300000
using namespace std;
int read()
{
    int sum=0,f=1;char x=getchar();
    while(x<'0'||x>'9'){if(x=='-')f=-1;x=getchar();}
    while(x>='0'&&x<='9'){sum=(sum<<1)+(sum<<3)+x-'0';x=getchar();}
    return sum*f;
}
int n,m,adj[N],f[N],vis[N],dep[N],anc[N],ans[N];
int tim[N],A[N],sum[N],up[N*2],down[N*2];
int e,e1,e2,e3,e4;
int adj1[N*2],adj2[N*2],adj3[N*2],head[N];
struct Q{int v,next,id;}q[N*2];
struct road{int v,next;}lu[N*2],lu1[N*2],lu2[N*2],lu3[N*2];
void add(int u,int v){lu[++e].v=v;lu[e].next=adj[u];adj[u]=e;}
void add1(int u,int v){lu1[++e1].v=v;lu1[e1].next=adj1[u];adj1[u]=e1;}
void add2(int u,int v){lu2[++e2].v=v;lu2[e2].next=adj2[u];adj2[u]=e2;}
void add3(int u,int v){lu3[++e3].v=v;lu3[e3].next=adj3[u];adj3[u]=e3;}
void add_q(int u,int v,int id){q[e4].v=v;q[e4].id=id;q[e4].next=head[u];head[u]=e4++;}
int find(int x){return f[x]==x? x:f[x]=find(f[x]);}
void hb(int x,int y){int fx=find(x),fy=find(y);if(fx!=fy)f[fx]=fy;}
void dfs1(int x,int fa)
{
    dep[x]=dep[fa]+1;anc[x]=x;vis[x]=1;
    for(int i=adj[x];i;i=lu[i].next)
    {
        int to=lu[i].v;
        if(vis[to])continue;
        dfs1(to,x);
        hb(x,to);anc[find(x)]=x;
    }
    for(int i=head[x];i!=-1;i=q[i].next)
    {
        int to=q[i].v;
        if(vis[to])ans[q[i].id]=anc[find(to)];
    }
}
void dfs2(int x,int y)
{
    int t1=up[dep[x]+tim[x]],t2=down[dep[x]-tim[x]+N];
    up[dep[x]]+=A[x];
    for(int i=adj1[x];i;i=lu1[i].next)
       ++down[lu1[i].v+N];
    for(int i=adj[x];i;i=lu[i].next)
       if(lu[i].v!=y)dfs2(lu[i].v,x);
    sum[x]=up[dep[x]+tim[x]]+down[dep[x]-tim[x]+N]-t1-t2;
    for(int i=adj2[x];i;i=lu2[i].next)
    {
        int to=lu2[i].v;
        up[to]--;
        if(to==dep[x]+tim[x])
            sum[x]--;
    }
    for(int i=adj3[x];i;i=lu3[i].next)
         down[lu3[i].v+N]--;
}
int main()
{
//  freopen("runninga.in","r",stdin);
//  freopen("runninga.out","w",stdout);
    int __size__=64<<20; 
    char *__p__=(char*)malloc(__size__)+__size__;  
    __asm__("movl %0, %%esp\n"::"r"(__p__));  
    n=read();m=read();int x,y,a,b;
    memset(head,-1,sizeof(head));
    for(int i=1;i<=n;i++)f[i]=i;
    for(int i=1;i<n;i++)x=read(),y=read(),add(x,y),add(y,x);
    for(int i=1;i<=n;i++)tim[i]=read();
    for(int i=0;i<m;i++)x=read(),y=read(),add_q(x,y,i),add_q(y,x,i);
    dfs1(1,0);
    for(int i=0;i<m;i++)
    {
        x=q[i<<1].v,y=q[i<<1|1].v;
        a=dep[x]+dep[y]-dep[ans[i]]*2;b=dep[x]-a;
        ++A[y];add1(x,b);add2(ans[i],dep[y]);add3(ans[i],b);
    }
    dfs2(1,0);
    for(int i=1;i<=n;i++)printf("%d ",sum[i]);
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值