NOIP2016 天天爱跑步 正解

暴力移步 http://www.cnblogs.com/TheRoadToTheGold/p/6673430.html

首先解决本题应用的知识点:

dfs序——将求子树的信息(树形)转化为求一段连续区间信息(线形)

线段树——求区间信息

树上差分——统计答案

lca——拆分路径

树链剖分——求lca

另deep[]表示节点的深度,watch[]表示观察者的出现时间,s表示玩家起点,t表示终点

固定节点的观察者出现的时间固定,说明对这个观察者有贡献的点是有限且固定的

只有满足  观察者出现时间=玩家起点与观察者距离的  玩家才对观察者有贡献

每条路径拆成   起点到lca(向上跑)  和   终点到lca的子节点(向下跑)  的两条路径

对于向上跑的,如果玩家能被观察员i观察到,那么deep[s]-deep[i]=watch[i]   式①

对于向下跑的,就是 deep[s]+deep[i]-2*deep[lca(s,i)]=watch[i]  式②

等号左边是玩家起点与观察者的距离,等号右边是观察者出现时间

向上跑的很显然,向下跑的如何理解?

假设我们知道点a,b到lca(a,b)的距离分别为da,db,那么a,b之间的距离=da+db

但这里的deep不是到lca的距离,是深度,即到根节点的距离+1

deep[s]+deep[i]包含2段信息,1、s到i的距离,   2、lca(s,i)到根节点的距离+1

第2段包含了2次,所以减去

先看向上跑的

玩家路径:玩家起点 到 起点与终点的lca

将式①移项,deep[s]=deep[i]+watch[i]

发现等号右边是定值

也就是说对与观察者i,他所能观察到的向上跑的玩家,是所有的起点=deep[i]+watch[i]的玩家

换句话说,以i为根的子树中,所有深度为deep[i]+watch[i]的玩家都能被i观察到

我们如果搞一个dfs序,i的在a时入栈,在b时出栈,

那么以i为根的子树就可以转化为区间[a,b]

深度咋整?

我们对每个深度建立一颗线段树(动态加点)

那么问题就转化为了  在深度为deep[i]+watch[i]的线段树中,查询区间[a,b]的玩家个数

现在就差玩家个数了

很容易想到在起点处+1

但是还要在起点与终点的lca的父节点处-1

差分惯用思想

用sum[]统计这些1和-1的和

那么问题就转化为了  在深度为deep[i]+watch[i]的线段树中,查询区间[a,b]的sum和

提问:为什么是起点处+1,lca的父节点处-1,可以反过来吗?

不可以。

因为起点的深度深,lca的父节点深度浅,在深度深的节点处+1,以深度深度浅的点为根的子树可以包含这个点

想想序列上的差分,是左端点+1,右端点后面的点-1

因为序列差分与前缀和相联系,前面的点的信息对后面的点会产生影响,所以只需加一个1

这里查询的是子树信息,是这个点深度及以下的信息

对照理解即可

向下跑的同理,只简单说怎么做

玩家路径:lca的子节点到玩家终点

把式②移项 deep[s]-2*deep[lca(s,i)]=watch[i]-deep[i]

在watch[i]-deep[i]深度为deep[s]-2*deep[lca(s,i)]的线段树中,终点处+1,lca处-1

查询时查深度为watch[i]-deep[i]的线段树即可

2个小问题:

1、做完向上跑的后,不要忘了清空线段树

2、向下跑的deep[s]-2*deep[lca(s,i)]可能会产生负数,所以全体后移一定长度,root[]数组开大

我后移了2*n,那么root[]数组要开3倍

#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 300401
using namespace std;
int n,m,fa[N],son[N],deep[N],bl[N],sz,id[N],ans[N];
int in[N],out[N],watch[N];
int front[N],nextt[N*2],to[N*2];
int root[N*3],lc[N*25],rc[N*25],sum[N*25],tot,cnt;
struct node
{
    int s,t,lca;
}runner[N];
void add(int u,int v)
{
    to[++cnt]=v; nextt[cnt]=front[u]; front[u]=cnt;
}
void dfs1(int now)
{
    son[now]++;
    for(int i=front[now];i;i=nextt[i])
    {
        if(to[i]==fa[now]) continue;
        deep[to[i]]=deep[now]+1;
        fa[to[i]]=now;
        dfs1(to[i]);
        son[now]+=son[to[i]];
    }
}
void dfs2(int now,int chain)
{
    id[now]=++sz;
    in[now]=sz;
    bl[now]=chain;
    int y=0;
    for(int i=front[now];i;i=nextt[i])
    {
        if(to[i]==fa[now]) continue;
        if(son[to[i]]>son[y]) y=to[i];
    }
    if(!y)
    {
        out[now]=sz;
        return;
    }
    dfs2(y,chain);
    for(int i=front[now];i;i=nextt[i])
    {
        if(to[i]==fa[now]||to[i]==y) continue;
        dfs2(to[i],to[i]);
     }
     out[now]=sz;
}
int getlca(int u,int v)
{
    while(bl[u]!=bl[v])
    {
        if(deep[bl[u]]<deep[bl[v]]) swap(u,v);
        u=fa[bl[u]];
    }
    return deep[u]<deep[v] ? u:v;
}
void change(int & now,int l,int r,int pos,int w)
{
    if(!pos) return;
    if(!now) now=++tot;
    sum[now]+=w;
    if(l==r) return;
    int mid=l+r>>1;
    if(pos<=mid) change(lc[now],l,mid,pos,w);
    else change(rc[now],mid+1,r,pos,w);
}
int query(int now,int l,int r,int opl,int opr)
{
    if(!now) return 0;
    if(l==opl&&r==opr) return sum[now];
    int mid=l+r>>1;
    if(opr<=mid) return query(lc[now],l,mid,opl,opr);
    else if(opl>mid) return query(rc[now],mid+1,r,opl,opr);
    else return query(lc[now],l,mid,opl,mid)+query(rc[now],mid+1,r,mid+1,opr);
}
void clear()
{
    tot=0;
    memset(lc,0,sizeof(lc));
    memset(rc,0,sizeof(rc));
    memset(sum,0,sizeof(sum));
    memset(root,0,sizeof(root));
}
int main()
{
    /*freopen("runninga.in","r",stdin);
    freopen("runninga.out","w",stdout);*/
    scanf("%d%d",&n,&m);
    int u,v;
    for(int i=1;i<n;i++)
    {
        scanf("%d%d",&u,&v);
        add(u,v);add(v,u);
    }
    for(int i=1;i<=n;i++) scanf("%d",&watch[i]);
    for(int i=1;i<=m;i++) scanf("%d%d",&runner[i].s,&runner[i].t);
    dfs1(1);
    dfs2(1,0);
    for(int i=1;i<=m;i++) runner[i].lca=getlca(runner[i].s,runner[i].t);
    int now;
    for(int i=1;i<=m;i++)
    {
        now=deep[runner[i].s];
        change(root[now],1,n,id[runner[i].s],1);
        change(root[now],1,n,id[fa[runner[i].lca]],-1);
    }
    for(int i=1;i<=n;i++) ans[i]=query(root[deep[i]+watch[i]],1,n,in[i],out[i]);
    clear();
    for(int i=1;i<=m;i++)
    {
        now=deep[runner[i].s]-deep[runner[i].lca]*2+n*2;
        change(root[now],1,n,id[runner[i].t],1);
        change(root[now],1,n,id[runner[i].lca],-1);
    }
    for(int i=1;i<=n;i++) ans[i]+=query(root[watch[i]-deep[i]+n*2],1,n,in[i],out[i]);
    for(int i=1;i<=n;i++) printf("%d ",ans[i]);
}
时间: 2024-09-30 01:45:11

NOIP2016 天天爱跑步 正解的相关文章

NOIP2016天天爱跑步

2557. [NOIP2016]天天爱跑步 时间限制:2 s   内存限制:512 MB [题目描述] 小C同学认为跑步非常有趣,于是决定制作一款叫做<天天爱跑步>的游戏.<天天爱跑步>是一个养成类游戏,需要玩家每天按时上线,完成打卡任务. 这个游戏的地图可以看作一棵包含n个结点和n-1条边的树,每条边连接两个结点,且任意两个结点存在一条路径互相可达.树上结点编号为从1到n的连续正整数. 现在有m个玩家,第i个玩家的起点为Si,终点为Ti.每天打卡任务开始时,所有玩家在第0秒同时从

bzoj4719[Noip2016]天天爱跑步

Description 小c同学认为跑步非常有趣,于是决定制作一款叫做<天天爱跑步>的游戏.?天天爱跑步?是一个养成类游戏,需要玩家每天按时上线,完成打卡任务.这个游戏的地图可以看作一一棵包含 N个结点和N-1 条边的树, 每条边连接两个结点,且任意两个结点存在一条路径互相可达.树上结点编号为从1到N的连续正整数.现在有个玩家,第个玩家的起点为Si ,终点为Ti  .每天打卡任务开始时,所有玩家在第0秒同时从自己的起点出发, 以每秒跑一条边的速度,不间断地沿着最短路径向着自己的终点跑去, 跑到

bzoj 4719: [Noip2016]天天爱跑步

Description 小c同学认为跑步非常有趣,于是决定制作一款叫做<天天爱跑步>的游戏.?天天爱跑步?是一个养成类游戏,需要 玩家每天按时上线,完成打卡任务.这个游戏的地图可以看作一一棵包含 N个结点和N-1 条边的树, 每条边连接两 个结点,且任意两个结点存在一条路径互相可达.树上结点编号为从1到N的连续正整数.现在有个玩家,第个玩家的 起点为Si ,终点为Ti  .每天打卡任务开始时,所有玩家在第0秒同时从自己的起点出发, 以每秒跑一条边的速度, 不间断地沿着最短路径向着自己的终点跑去

LCA+线段树 NOIP2016 天天爱跑步

天天爱跑步 题目描述 小c同学认为跑步非常有趣,于是决定制作一款叫做<天天爱跑步>的游戏.?天天爱跑步?是一个养成类游戏,需要玩家每天按时上线,完成打卡任务. 这个游戏的地图可以看作一一棵包含 nnn个结点和 n?1n-1n?1条边的树, 每条边连接两个结点,且任意两个结点存在一条路径互相可达.树上结点编号为从111到nnn的连续正整数. 现在有mmm个玩家,第iii个玩家的起点为 SiS_iS?i??,终点为 TiT_iT?i?? .每天打卡任务开始时,所有玩家在第000秒同时从自己的起点出

bzoj4719: [Noip2016]天天爱跑步 树上差分

Description 小c同学认为跑步非常有趣,于是决定制作一款叫做<天天爱跑步>的游戏.?天天爱跑步?是一个养成类游戏,需要 玩家每天按时上线,完成打卡任务.这个游戏的地图可以看作一一棵包含 N个结点和N-1 条边的树, 每条边连接两 个结点,且任意两个结点存在一条路径互相可达.树上结点编号为从1到N的连续正整数.现在有个玩家,第个玩家的 起点为Si ,终点为Ti .每天打卡任务开始时,所有玩家在第0秒同时从自己的起点出发, 以每秒跑一条边的速度, 不间断地沿着最短路径向着自己的终点跑去,

noip2016 天天爱跑步

分析:这道题真心烦啊,是我做过noip真题中难度最高的一道了,到今天为止才把noip2016的坑给填满.暴力的话前60分应该是可以拿满的,后40分还是很有难度的. 定义:每个人的起点.终点:s,t;深度:deep[i];观察员出现时间:w[i]; 首先,树上两个点的最短路径肯定要经过LCA,那么对于路径x ---> y我们可以分成两部分:1.x ---> lca. 2.lca ---> y.先分析第一段路上的观察员i,显然s到i的距离等于w[i]才行,这段路是从x向上跳的,所以可以得到式

BZOJ 4719 [Noip2016]天天爱跑步 ——树链剖分

一直以为自己当时是TLE了,但是再看发现居然WA? 然后把数组扩大一倍,就A掉了.QaQ 没什么好说的.一段路径分成两段考虑,上升的一段深度+时间是定值,下降的一段深度-时间是定值,然后打标记统计即可. 发现大概是统计数组因为深度+时间太大炸掉了. 现在想想,当时没有对拍,真是后怕. #include <cstdio> #include <vector> #include <cstring> #include <iostream> #include <

BZOJ 4719--天天爱跑步(LCA&amp;差分)

4719: [Noip2016]天天爱跑步 Time Limit: 40 Sec  Memory Limit: 512 MBSubmit: 1464  Solved: 490[Submit][Status][Discuss] Description 小c同学认为跑步非常有趣,于是决定制作一款叫做<天天爱跑步>的游戏.?天天爱跑步?是一个养成类游戏,需要 玩家每天按时上线,完成打卡任务.这个游戏的地图可以看作一一棵包含 N个结点和N-1 条边的树, 每条边连接两 个结点,且任意两个结点存在一条路

天天爱跑步[NOIP2016]

时间限制:2 s   内存限制:512 MB [题目描述] 小C同学认为跑步非常有趣,于是决定制作一款叫做<天天爱跑步>的游戏.<天天爱跑步>是一个养成类游戏,需要玩家每天按时上线,完成打卡任务. 这个游戏的地图可以看作一棵包含n个结点和n-1条边的树,每条边连接两个结点,且任意两个结点存在一条路径互相可达.树上结点编号为从1到n的连续正整数. 现在有m个玩家,第i个玩家的起点为Si,终点为Ti.每天打卡任务开始时,所有玩家在第0秒同时从自己的起点出发,以每秒跑一条边的速度,不间断