noip2016 day1 t2 天天爱跑步

题目

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

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

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

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

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

输入格式:

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

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

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

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

对于所有的数据,保证 1<=S_i,T_i<=n, 0< W_j <= n , 1≤Si​,Ti​≤n,0≤Wj​≤n 。

输出格式:

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

首先一条链一条链地跑肯定不行,我们想想一个点的答案是怎么被更新的

首先一个点在一个路径上,那么要么这个路径的起点在子树中,要么终点在子树中。

当这个点i被这个路径增加答案时,一定满足:

1 . 起点s在其子树里,那么dep[s] - dep[i] = w[i]

2 . 终点t在其子树里,那么len - (dep[t]-dep[i]) = w[i](len 为路径长度)

整理得到 dep[i] + w[i] =dep[s] , dep[i] - w[i] = dep[t] - len

所以我们要求的就是i子树中满足要求的s,t个数。

发现子树的统计可以递归,用两个桶装 dep[s] ,dep[t] - len 。 求这个点的值时直接从桶里取就好了,而lca为i的路径显然不会对i父亲做出贡献,因此我们在处理完i后将其贡献倒掉

我们先写个dfs的伪代码:

    dfs(i )

          遍历i的子树

          for(每条起点为i的路) 把dep[i]装进桶1

          for(每条终点为i的路) 把dep[i] - len装进桶2

          ans[i] = 桶1[ dep[i] + w[i] ] + 桶2[ dep[i]-w[i] ]

          for(每条lca为i的路)  把这些路的s与t做出的贡献倒掉 

最后的小问题   i的兄弟k先于i遍历,所以k的子树的贡献已经装进桶了,我们如何保证i不会被k的子树更新呢?

在遍历i之前先统计一个ans0[i] , 这样ans[i]-ans0[i]就是i子树的贡献了

还有诸多小细节我会在代码中解释

#include <cstdio>
#include <algorithm>
#include <cstring>
int fa[300005][21] ;
int n , m ,w[300005]  ;
int dep[300005],hd[300005] , tot , des[600005] , nxt[600005] , ans[300005]; 
struct pl{
    int st , en , lca , len ;
}plan[300005];
int tong1[600005] , tong2[600005] , ans0[300005];//邻接链表实现遍历
int hdst[300005] , ntst[300005] , nust[300005]; 
void gest ( int x , int num )
{
    tot++;
    nust[tot]=num;
    ntst[tot]=hdst[x];
    hdst[x]=tot;
}
int hden[300005] , nten[300005] , nuen[300005]; 
void geen ( int x , int num )
{
    tot++;
    nuen[tot]=num;
    nten[tot]=hden[x];
    hden[x]=tot;
}
int hdlca[300005] , ntlca[300005] , nulca[300005]; 
void gelca ( int x , int num )
{
    tot++;
    nulca[tot]=num;
    ntlca[tot]=hdlca[x];
    hdlca[x]=tot;
}//之后是跑lca
void gw(int x, int y) 
{
    tot++;
    des[tot] = y;
    nxt[tot] = hd[x];
    hd[x] = tot;
}
void dfs(int u,int f)
{
    for (int k=hd[u];k;k=nxt[k])
    {
        int v = des[k];
        if (v==f)continue ;
        fa[v][0]=u;
        dep[v]=dep[u]+1;
        dfs(v,u);
    }
}
void yuli( )
{
    for (int k=1;k<=20;k++)
        for (int i=1;i<=n;i++)
           	  fa[i][k]=fa[fa[i][k-1]][k-1];
}
int lc (int x, int y)
{
    if(dep[x]<dep[y])std::swap(x,y);
    int dt=dep[x]-dep[y],ko=0 ;
    while (dt)
    {
        if ( dt%2 ) x=fa[x][ko] ;
        ko++;
        dt=dt/2;
    }
    while (x!=y)
    {
        int i=0;
        while(fa[x][i+1]!=fa[y][i+1]){i++;}
        x=fa[x][i];
        y=fa[y][i];
    }
    return x;
}
void qdfs(int u,int f)//注意dep[u]-w[u]可能下溢,需要平移
{
    ans0[u] = tong1[dep[u]+w[u]] + tong2[dep[u]-w[u]+300000] ;	//之后与伪代码一致
    for (int k=hd[u];k;k=nxt[k])
    {
        int v = des[k];
        if (v==f)continue ;
        qdfs(v,u);
    }
    for (int k=hdst[u] ; k ; k=ntst[k]) tong1[dep[u]]++;
    for (int k=hden[u] ; k ; k=nten[k]) tong2[dep[u]-plan[nuen[k]].len+300000] ++; 
    ans[u] = tong1[dep[u]+w[u]] + tong2[dep[u]-w[u]+300000] ;
    for (int k=hdlca[u] ; k ; k=ntlca[k])
        {
        	tong1[dep[plan[nulca[k]].st]]--;
        	tong2[dep[plan[nulca[k]].en] - plan[nulca[k]].len+300000]--;
        }
}
int main()
{
    freopen("running.in","r",stdin);
    freopen("running.out","w",stdout);
    scanf("%d%d",&n,&m);
    for (int i=1;i<n;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        gw(x,y);
        gw(y,x);
    }
    dfs(1,1);
    yuli( ) ;
    for (int i=1;i<=n;i++) scanf("%d",&w[i]);
    for (int i=1;i<=m;i++)
    {
        scanf("%d%d",&plan[i].st,&plan[i].en);
        plan[i].lca = lc ( plan[i].st , plan[i].en ) ;
        plan[i].len = dep[plan[i].st] + dep[plan[i].en] - 2 * dep[plan[i].lca] ;
    }
    tot = 0;
    for (int i=1;i<=m;i++)
        gest(plan[i].st,i);
    tot = 0;
    for (int i=1;i<=m;i++)
        geen(plan[i].en,i);
    tot = 0;
    for (int i=1;i<=m;i++)
        gelca(plan[i].lca,i);
    qdfs (1,1);
    for (int i=1;i<=m;i++)//当lca恰好能被这条链更新时,这条链的s与t都加了一遍,因此要减去。
        if ( dep [plan[i].lca] + w[plan[i].lca] == dep[plan[i].st] ) ans[plan[i].lca] -- ;
    for (int i=1;i<=n;i++)
        printf("%d ",ans[i]-ans0[i]);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值