1. 程式人生 > >noip2016 天天愛跑步

noip2016 天天愛跑步

ini node 右移 += upd 時間 個人 方法 最短路

技術分享

技術分享

技術分享

技術分享

分析:這道題真心煩啊,是我做過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;
}

noip2016 天天愛跑步