[解题报告]ACM-ICPC Regionals 2011 Asia - Fuzhou C Bob's Race

Abstract

ACM-ICPC Regionals 2011 Asia - Fuzhou C Bob's Race

POJ4003

树形dp 单调队列

 

Body

Source

http://poj.org/problem?id=4003

Description

给定一棵N个节点的树,数列d[i] (1 <= i <= N)表示树中离点i最远的点离点i的距离。现给定Q,求d[i]的最长连续子序列的长度,使得其满足该子序列中最大最小值之差不超过Q。

Solution

这个题属于比较生硬的凑算法的题……问题可以分为两部分:

1. 求数列d[i]。这里用的是非常经典的一种树形dp方法,我以前不知道叫什么,今天据章总和戴牛说叫“上下树”(好猥琐……),长春邀请赛也出了,有空可以写个专题。这种方法在对树的每个节点进行全局统计且要求复杂度为O(N)时经常用到。“上下树”的名称大概来源于这种树形dp进行两次dp,第一次自底向上(叶到根),第二次自顶向下(根到叶)。对于此题,考虑将一个点拎出来作树根后,某个点v的最远距离,要么是v向子孙节点走的最远距离,要么是其父节点u不经过(u,v)的最远距离+w(u,v)。可以把边(u,v)看作分界,u在左v在右,则前者是v往右走的最远距离,后者是v往左走的最远距离。

因此,第一次树形dp要求出点u向子孙节点走的最远距离max1[u],向哪个子节点走最远(设为maxv[u])以及向子孙节点走的次远距离max2[u](为何要求接下来说明)。第二次树形dp对于点v,d[v]=max(x, max1[v]),其中x为v的父节点不经过(u,v)的最远距离+w(u,v)根据v是否是maxv[u],x是不一样的,因此可能用到向哪个子节点走最远(设为maxv[u])以及向子孙节点走的次远距离max2[u]。

2. 求d[i]的最长连续子序列的长度。网上很多说用RMQ做,我太弱搞不懂为啥会用到RMQ,我用的是单调队列做的。

显然应考察以d[i]结尾的最长子序列。假设已知以d[i-1]结尾的最长子序列X,为了求d[i]结尾的最长子序列Y,我们还需要知道X集中的最大数,设为d[j]。若|d[i]-d[j]|>Q则Y的起始下标>j且应继续考察d[j]后的最大数d[j']。若|d[i]-d[j']|>Q则Y的起始下标>j'且应继续考察d[j']后的最大数d[j'']等等。类似的还需要知道X集中的最小数d[k],d[k]后最小数d[k'],d[k']后最小数d[k'']等。这样容易得到Y。这种信息要结构化存储,而且加入d[i]后还要O(1)转移,你想到什么,反正我想到的是单调队列。事实上d[j],d[j'],d[j'']...构成的就是一个单调增队列。

顺便,那次比赛留下了无可挽回的遗憾,家乡真是我永远的伤心之地……

Code

注意,STL的deque会被POJ猥琐卡常数,必须手写。

#include <cstdio>

#include <cstring>

#include <vector>

#include <queue>

#include <algorithm>

using namespace std;



int N, M, Q, ans, st;

vector<int> adj[50050], w[50050];

int max1[50050], max2[50050], maxv[50050];

int d[50050];

int inc[50050],ih, it, dec[50050], dh, dt;



void init()

{

    for (int i = 1; i <= N; ++i)

    {

        adj[i].clear();

        w[i].clear();

    }

}



inline void ae(int u, int v, int d)

{

    adj[u].push_back(v);

    w[u].push_back(d);

    adj[v].push_back(u);

    w[v].push_back(d);

}



void dfs1(int f, int u)

{

    max1[u] = max2[u] = maxv[u] = 0;

    int v, tmp;

    for (int i = 0; i < adj[u].size(); ++i)

    {

        v = adj[u][i];

        if (v==f) continue;

        dfs1(u, v);

        tmp = max1[v]+w[u][i];

        if (tmp >= max1[u])

        {

            max2[u] = max1[u];

            max1[u] = tmp;

            maxv[u] = v;

        }

        else if (tmp > max2[u])

            max2[u] = tmp;

    }

}





void dfs2(int f, int u, int l)

{

    d[u] = max(l, max1[u]);

    int v, tmp;

    for (int i = 0; i < adj[u].size(); ++i)

    {

        v = adj[u][i];

        if (v==f) continue;

        if (v != maxv[u]) dfs2(u, v, d[u]+w[u][i]);

        else dfs2(u, v, max(max2[u], l)+w[u][i]);

    }

}



int main()

{

    int i, j, k, u, v;

    while (scanf("%d%d", &N, &M), N||M)

    {

        init();

        for (i = 1; i < N; ++i)

        {

            scanf("%d%d%d", &u, &v, &k);

            ae(u, v, k);

        }

        dfs1(0, 1);

        dfs2(0, 1, 0);

        while (M--)

        {

            scanf("%d", &Q);

            ans = 1;

            ih = it = dh = dt = 0;

            inc[it++] = 1;

            dec[dt++] = 1;

            st = 0;

            for (i = 2; i <= N; ++i)

            {

                j = k = st;

                while (ih!=it && abs(d[i]-d[inc[ih]])>Q)

                    j = inc[ih++];

                while (dh!=dt && abs(d[i]-d[dec[dh]])>Q)

                    k = dec[dh++];

                st = max(j, k);

                ans = max(ans, i-st);

                while (ih!=it && d[i]<=d[inc[it-1]]) it--;

                inc[it++] = i;

                while (dh!=dt && d[i]>=d[dec[dt-1]]) dt--;

                dec[dt++] = i;

            }

            printf("%d\n", ans);

        }

    }

    return 0;

}

你可能感兴趣的:(ICPC)