noip2016 天天爱跑步

Posted zbtrs

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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标记.为什么要-1呢?我们可以这么理解:我要查询i的子树,如果i不在第一段路上,我如果不-1,那么就会统计进入答案.

     操作完后,每次查询深度为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;
}

 

 

 

以上是关于noip2016 天天爱跑步的主要内容,如果未能解决你的问题,请参考以下文章

天天爱跑步[NOIP2016]

NOIP2016天天爱跑步

NOIP2016 天天爱跑步

noip 2016 天天爱跑步

LCA+线段树 NOIP2016 天天爱跑步

NOIP2016天天爱跑步