天天爱跑步 [NOIP2016]

Description

小c同学认为跑步非常有趣,于是决定制作一款叫做《天天爱跑步》的游戏。《天天爱跑步》是一个养成类游戏,需要玩家每天按时上线,完成打卡任务。这个游戏的地图可以看作一一棵包含 N个结点和N-1 条边的树, 每条边连接两个结点,且任意两个结点存在一条路径互相可达。树上结点编号为从1到N的连续正整数。现在有个玩家,第个玩家的起点为Si ,终点为Ti  。每天打卡任务开始时,所有玩家在第0秒同时从自己的起点出发, 以每秒跑一条边的速度,不间断地沿着最短路径向着自己的终点跑去, 跑到终点后该玩家就算完成了打卡任务。 (由于地图是一棵树, 所以每个人的路径是唯一的)小C想知道游戏的活跃度, 所以在每个结点上都放置了一个观察员。 在结点的观察员会选择在第Wj秒观察玩家, 一个玩家能被这个观察员观察到当且仅当该玩家在第Wj秒也理到达了结点J  。 小C想知道每个观察员会观察到多少人?注意: 我们认为一个玩家到达自己的终点后该玩家就会结束游戏, 他不能等待一 段时间后再被观察员观察到。 即对于把结点J作为终点的玩家: 若他在第Wj秒重到达终点,则在结点J的观察员不能观察到该玩家;若他正好在第Wj秒到达终点,则在结点的观察员可以观察到这个玩家。

Input

第一行有两个整数n和m。其中n代表树的结点数量,同时也是观察员的数量,m代表玩家的数量。
接下来n-1行每行两个整数u和v,表示结点u到结点v有一条边。
接下来一行n个整数,其中第j个整数为Wj,表示结点j出现观察员的时间。
接下来m行,每行两个整数Si和Ti,表示一个玩家的起点和终点。
对于所有的数据,保证1≤Si,Ti≤n,0≤ Wj ≤n。

Output

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

Sample Input

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

Sample Output

2 0 0 1 1 1

Hint

提示:
对于1号点,W1=0,故只有起点为1号点的玩家才会被观察到,所以玩家1和玩家2被观察到,共2人被观察到。
对于2号点,没有玩家在第2秒时在此结点,共0人被观察到。
对于3号点,没有玩家在第5秒时在此结点,共0人被观察到。
对于4号点,玩家1被观察到,共1人被观察到。
对于5号点,玩家2被观察到,共1人被观察到。
对于6号点,玩家3被观察到,共1人被观察到。

Pic

如果你的程序需要用到较大的栈空间(这通常意味着需要较深层数的递归),请务必仔细阅读选手目录下的文档running/stackpdf,以了解在最终评测时栈空间的限制与在当前工作环境下调整栈空间限制的方法。

Solution

以下部分分算法引自 传送门

测试点1——5:

数组a[i][j]表示点i在j时刻经过的人数,然后一个人一个人的dfs,记录时刻time

如果到了终点,递归回退时a[now][time]++

令watch[i]=j表示i号节点观察员在j时刻出现

输出a[i][watch[i]]

 1 void run(int now,int end,int time,int fa)
 2 {
 3     if(now==end)
 4     {
 5         ok=true;
 6         a[now][time]++;
 7         return;
 8     }
 9     if(ok) return;
10     for(int i=front[now];i;i=nextt[i])
11     {
12         if(to[i]==fa) continue;
13         run(to[i],end,time+1,now);
14     }
15     if(ok) a[now][time]++;
16 }
25分

 

测试点6——8:

树退化为一条链,而且点的编号就是深度

所以对于链上一个观察员,他只能观察到从两个位置出发的人i+watch[i],i-watch[i]

如果i能观察到起点深度比他小的人,那么这个人的终点要>=i

如果i能观察到起点深度比他大的人,那么这个人的终点要<=i

所以,

用链表存储从每个节点起跑的人,

枚举每个观察员,然后判断两个位置的人是否满足要求

 1 void solve2()
 2 {
 3     for(int i=1;i<=n;i++)
 4     {
 5         if(i-watch[i]>=1)
 6         {
 7             for(int j=front[i-watch[i]];j;j=nextt[j])
 8             {
 9                 if(i<=e[to[j]].t)  ans[i]++;
10             }
11         }
12         if(i+watch[i]<=n)
13         {
14             for(int j=front[i+watch[i]];j;j=nextt[j])
15             {
16                 if(i>=e[to[j]].t) ans[i]++;
17             }
18         } 
19     }
20 }
40分

 

测试点9——12:

所有人的起点都是1,假设1号的深度为0,

那么只有观察员节点的深度等于出现时间,他才能观察到

所以先判断深度是否等于出现时间,不等,直接输出0,下一个

若相等,他一定能观察到经过他的所有人

也就是说,以观察员i为根的子树中有跑步者的终点,观察员i就能观察多少人

所以,终点+1,记录每个节点的深度,我用的bfs,单后dfs统计子树1的个数,

kids[]表示答案

 1 void bfs1()
 2 {
 3     queue<node2>q;
 4     cur.d=0 ;cur.who=1; fa[1]=0;
 5     q.push(cur);
 6     while(!q.empty())
 7     {
 8         cur=q.front(); q.pop();
 9         for(int i=front[cur.who];i;i=nextt[i])
10         {
11             if(to[i]==fa[cur.who]) continue;
12             fa[to[i]]=cur.who;
13             nxt.d=cur.d+1; nxt.who=to[i]; deep[nxt.who]=deep[cur.who]+1;
14             q.push(nxt); 
15         }
16     }
17 }
18 void dfs1(int now)
19 {
20     kids[now]=sum[now];
21     for(int i=front[now];i;i=nextt[i])
22     {
23         if(to[i]==fa[now]) continue;
24         dfs1(to[i]);
25         kids[now]+=kids[to[i]];
26     }
27 }
60分

 

测试点13——16:

所有人的终点都是1

也就是说,第i个观察员只能观察到第deep[i]+watch[i]层的跑步者

所以,若跑步者能被观察员i观察到,要满足以下两个条件:

1、在起点观察员i的子树内

2、起点的层数=deep[i]+watch[i]

先对原树dfs一遍,记录观察员i的子树dfs序范围in[i]——out[i]

对每一层维护一颗线段树,动态开节点

查询时,找到deep[i]+watch[i]这一层的线段树,查in[i]——out[i]有多少个起点

 1 void add(int &now,int pos,int l,int r,int cnt)
 2 {
 3     if(!now) now=++tot2;
 4     sum[now]+=cnt;
 5     if(l==r) return;
 6     int mid=l+r>>1;
 7     if(pos<=mid) add(lc[now],pos,l,mid,cnt);
 8     else add(rc[now],pos,mid+1,r,cnt);
 9 }
10 void dfs(int now,int pre)
11 {
12     in[now]=++tot;
13     add(root[deep[now]],tot,1,n,siz[now]);
14     for(int i=front[now];i;i=nxt[i])
15     {
16         if(to[i]==pre) continue;
17         deep[to[i]]=deep[now]+1;
18         maxn=max(maxn,deep[to[i]]);
19         dfs(to[i],now);
20     }
21     out[now]=tot;
22 }
23 void query(int now,int opl,int opr,int l,int r)
24 {
25     if(!now) return;
26     if(l>=opl&&r<=opr) { ans+=sum[now]; return; }
27     int mid=l+r>>1;
28     if(opl<=mid) query(lc[now],opl,opr,l,mid);
29     if(opr>mid) query(rc[now],opl,opr,mid+1,r);
30 }
View Code

 

 

-----------------------------------------------引用内容结束-----------------------------------------------------

 

正解

 对于一条路径u->v,我们可以剖成往上和往下的两部分

对于向上的u->lca,如果中间有点i满足dep[i]+w[i]==dep[u],则这条路径会对i的答案产生贡献

对于向下的lca->v,如果中间有点i满足dep[i]-w[i]==dep[v]-dis(u,v),则这条路径会对i的答案产生贡献

那么我们考虑在dfs时维护每一层的起点数,对于向上的时候,对答案产生贡献开始于u点,终止于lca点,对于向下的时候,对答案产生贡献开始于v点,终止于lca点。

那么我们在一开始用三个数组v1(i)记录下路径lca为i的起点深度,v2(i)记录下路径终点为i的终点深度-dis(路径起点,路径终点),v3(i)记录下路径lca为i的终点深度-dis(路径起点,路径终点)

我们就可以计算向上跑时,在dfs每一个点的时候 向该层加入该点的路径数,统计答案,删除以该点为lca的路径数

同理向下跑 向该层加入终点为该点的路径数,统计答案,删除以该点为lca的路径数

 

Code

#include<bits/stdc++.h>
#define RG register int
#define rep(i,a,b)    for(RG i=a;i<=b;++i)
#define add(u,v) e[++cnt].v=v,e[cnt].next=head[u],head[u]=cnt,swap(u,v),e[++cnt].v=v,e[cnt].next=head[u],head[u]=cnt
#define maxn 300100
#define WY 300000
using namespace std;
int n,m,cnt;
int fa[maxn],son[maxn],sz[maxn],top[maxn],dep[maxn],head[maxn],num[maxn],tot[maxn<<1],ans[maxn],w[maxn];
vector<int> v1[maxn],v2[maxn],v3[maxn];
struct E{
    int v,next;
}e[maxn<<1];
inline int read()
{
    int x=0,f=1;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
    return x*f;
}
void dfs1(int u,int pa)
{
    sz[u]=1,fa[u]=pa,dep[u]=dep[pa]+1;
    for(int i=head[u];i;i=e[i].next)
    {
        int v=e[i].v;    if(v==pa)continue;
        dfs1(v,u);        sz[u]+=sz[v];
        if(sz[son[u]]<sz[v])    son[u]=v;
    }
}
void dfs2(int u,int tp)
{
    top[u]=tp;
    if(!son[u])    return;dfs2(son[u],tp);
    for(int i=head[u];i;i=e[i].next)
        if(e[i].v!=fa[u]&&e[i].v!=son[u])    dfs2(e[i].v,e[i].v);
}
int find(int x,int y)
{
    while(top[x]!=top[y]){
        if(dep[top[x]]<dep[top[y]])    swap(x,y); x=fa[top[x]];
    }
    return dep[x]<dep[y]?x:y;
}
void go_up(int u)
{
    int len=dep[u]+w[u];    //计算 离 对目前位置可以产生贡献的起点 的距离
    ans[u]-=tot[len];          //减去其他子树中的数量   
    for(int i=head[u];i;i=e[i].next)
        if(e[i].v!=fa[u])    go_up(e[i].v);
    tot[dep[u]]+=num[u];  //加入目前点的起点数
    ans[u]+=tot[len];         //计算贡献
    for(int i=0,lim=v1[u].size();i<lim;i++)    --tot[v1[u][i]]; //减去桶中lca为该点的统计
}
void go_down(int u)
{
    int len=dep[u]-w[u]+WY; //加上定值防止len<0
    ans[u]-=tot[len];            //减去其他子树中的数量
    for(int i=head[u];i;i=e[i].next)
        if(e[i].v!=fa[u])    go_down(e[i].v);
    for(int i=0,lim=v2[u].size();i<lim;i++)    ++tot[v2[u][i]];  //加入范围内(v==now)的点
    ans[u]+=tot[len];
    for(int i=0,lim=v3[u].size();i<lim;i++)    --tot[v3[u][i]];    //减去范围外(lca==now)的点
}
int main()
{
    n=read(),m=read();
    for(RG i=1,u,v;i<n;i++)    u=read(),v=read(),add(u,v);
    dfs1(1,0);dfs2(1,0);
    rep(i,1,n)    w[i]=read();
    rep(i,1,m)
    {
        int u=read(),v=read(),lca=find(u,v);
        ++num[u];int len=dep[u]+dep[v]-(dep[lca]<<1);
        v1[lca].push_back(dep[u]),v2[v].push_back(dep[v]-len+WY),v3[lca].push_back(dep[v]-len+WY);
        if(w[lca]+dep[lca]==dep[u])    --ans[lca];
    }
    go_up(1);memset(tot,0,sizeof(tot));
    go_down(1);
    rep(i,1,n)    printf("%d ",ans[i]);
    return 0;
}

 

 

posted @ 2017-10-30 22:23  iBilllee  阅读(221)  评论(0编辑  收藏  举报