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向上跳的,所以可以得到式子:

deep[s] - deep[i] = w[i].

移项,得到:

deep[s] = deep[i] + w[i].

可以发现右边是固定的,那么我们只需要找出以i为根的子树中深度为deep[i] + w[i]的点中,有多少起点.对于子树的修改查询,我们通常转换为区间来做,方法是dfs序+线段树。只是这个线段树的姿势有点特别:动态加点,每一个深度建一棵线段树。

现在我们把第一段路简化为区间[a,b],当有一个人的起点满足条件时,我们要使[a,b] + 1,具体怎么实现呢?可以参考noip2012借教室,利用差分思想,在s[a]处+1,在s[b + 1]处-1,这个操作是在线段树上完成的,这样就避免了区间修改,而变成了单点修改,主要是为了避免使用lazy标记.

操作完后,每次查询深度为deep[i] + w[i]的深度的点中有多少起点就好了.

下面分析第二段路,其实操作类似,不过就是式子变了:

deep[s] + deep[i] - 2*deep[lca(s,i)] = w[i].

这个式子也是比较显然的,在图上推一下就可以得到,移项也可以得到:

deep[s] - 2*deep[lca(s,i)] = w[i] - deep[i]

然后的处理方法就和上面是一样的,不过要注意,式子左边可能会等于负数,那么我们将下标都向右移2*n位就可以了.

参考:传送门,这位神犇用的方法是树链剖分求lca,不过我的是用的倍增,其实都差不多啦.

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<cmath>
#include<map>

using namespace std;

const int inf = 0x7ffffff;

int n, m, tot = 1, head[300010], to[600010], nextt[600010], w[300010], dfs_clock, in[300010], out[300010], deep[300010], fa[300010][21], id[300010];
int root[300010 * 3], lc[300010 * 25], rc[300010 * 25], ans[300010], cnt, sum[300010 * 25];

struct node
{
    int s, t, lca;
}e[300010];

void add(int x, int y)
{
    to[tot] = y;
    nextt[tot] = head[x];
    head[x] = tot++;
}

void dfs(int u, int d, int from)
{
    in[u] = ++dfs_clock;
    id[u] = dfs_clock;
    deep[u] = d;
    for (int i = head[u]; i; i = nextt[i])
    {
        int v = to[i];
        if (v != from)
        {
            dfs(v, d + 1, u);
            fa[v][0] = u;
        }
    }
    out[u] = dfs_clock;
}

int getlca(int x, int y)
{
    if (x == y)
        return x;
    if (deep[x] < deep[y])
        swap(x, y);
    for (int j = 19; j >= 0; j--)
        if (deep[fa[x][j]] >= deep[y])
            x = fa[x][j];
    if (x == y)
        return x;
    for (int j = 19; j >= 0; j--)
        if (fa[x][j] != fa[y][j])
        {
            x = fa[x][j];
            y = fa[y][j];
        }
    return fa[x][0];
}

void init()
{
    cnt = 0;
    memset(lc, 0, sizeof(lc));
    memset(rc, 0, sizeof(rc));
    memset(sum, 0, sizeof(sum));
    memset(root, 0, sizeof(root));
}

void update(int &o, int l, int r, int p, int v)
{
    if (!p)
        return;
    if (!o)
        o = ++cnt;
    sum[o] += v;
    if (l == r)
        return;
    int mid = (l + r) >> 1;
    if (p <= mid)
        update(lc[o], l, mid, p, v);
    else
        update(rc[o], mid + 1, r, p, v);
}

int query(int o, int l, int r, int x, int y)
{
    if (!o)
        return 0;
    if (x <= l && r <= y)
        return sum[o];
    int mid = (l + r) >> 1, res = 0;
    if (x <= mid)
    res += query(lc[o], l, mid, x, y);
    if (y > mid)
    res += query(rc[o], mid + 1, r, x, y);
    return res;
}

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i < n; i++)
    {
        int u, v;
        scanf("%d%d", &u, &v);
        add(u, v);
        add(v, u);
    }
    for (int i = 1; i <= n; i++)
        scanf("%d", &w[i]);
    for (int i = 1; i <= m; i++)
        scanf("%d%d", &e[i].s, &e[i].t);
    dfs(1, 1, 0); //预处理出dfs序和深度和fa数组
    /*
    for (int i = 1; i <= n; i++)
    printf("%d %d %d\n", i, in[i], out[i]);
    */
    for (int j = 1; j <= 19; j++)
        for (int i = 1; i <= n; i++)
            fa[i][j] = fa[fa[i][j - 1]][j - 1];
    for (int i = 1; i <= m; i++)
        e[i].lca = getlca(e[i].s, e[i].t);
    for (int i = 1; i <= m; i++)
    {
        int tt = deep[e[i].s];
        update(root[tt], 1, n, id[e[i].s], 1);
        update(root[tt], 1, n, id[fa[e[i].lca][0]], -1);
    }
    for (int i = 1; i <= n; i++)
        ans[i] = query(root[deep[i] + w[i]], 1, n, in[i], out[i]);
    init(); //第一次线段树后一定要清空
    for (int i = 1; i <= m; i++)
    {
        int tt = deep[e[i].s] - deep[e[i].lca] * 2 + n * 2;
        update(root[tt], 1, n, id[e[i].t], 1);
        update(root[tt], 1, n, id[e[i].lca], -1); //关于这里为什么不用fa[lca][0],目的是为了避免重复计算lca的贡献.
    }
    for (int i = 1; i <= n; i++)
        ans[i] += query(root[w[i] - deep[i] + n * 2], 1, n, in[i], out[i]);
    for (int i = 1; i <= n; i++)
        printf("%d ", ans[i]);

    return 0;
}
时间: 2024-10-06 00:16:35

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 天天爱跑步 正解

暴力移步 http://www.cnblogs.com/TheRoadToTheGold/p/6673430.html 首先解决本题应用的知识点: dfs序——将求子树的信息(树形)转化为求一段连续区间信息(线形) 线段树——求区间信息 树上差分——统计答案 lca——拆分路径 树链剖分——求lca 另deep[]表示节点的深度,watch[]表示观察者的出现时间,s表示玩家起点,t表示终点 固定节点的观察者出现的时间固定,说明对这个观察者有贡献的点是有限且固定的 只有满足  观察者出现时间=玩

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秒同时从自己的起点出发,以每秒跑一条边的速度,不间断