Poj 3162 Walking Race (DP_树形DP(线段树))

题目链接:http://poj.org/problem?id=3162

题目大意:给定一张地图,它是一棵n个节点的树。mm爱跑步,mm要跑n天,每次都从一个结点开始跑步,每次都要跑到最远的那个结点,两天跑的最远距离有个差值,现在要从这n天里去若干天使得这些天的差值都小于m,问怎么取使得天数最多?n <= 100万,m <= 1亿。

解题思路:树形DP + 线段树。虽然数据量很吓人,但经过分析发现这题就是几个算法的聚合体,没有特别难。

     1、先用树形dp思想把每个点到其他某点的最远距离求出来。具体做法是先把树变成有根树,深搜一遍找出每个节点到叶子的最长距离,这样算出来的是向下的最远距离,但还有一个向上即向父亲的那条分支的距离未纳进来计算。之后,再搜一遍把向上的那条分支也算进来,深搜和广搜都可以做。代码写得比较清晰,可以看下代码。这一步其实就是Hdu 2196 Computer,之前做过那题觉得这步还不是很难。

     2、上一步得到一个数组,现在要从这个数组里找出连续的一段序列最大值最小值之差小于m,并且长度尽量大。暴力枚举肯定超时,100万*100万的计算量,100s可以算完吧。只能考虑O(N)的算法,因为是找最长的一个序列,我们可以通过维护两个指针来完成这个计算过程,两个指针表示区间的开始和结束,如果这个区间差值=<m,那么区间尾可以下移,如果>m那么区间必须缩小一些,区间头向后移,这样就可以通过不断增大区间头使得这个区间差值<=m.

     3、那我们怎么找区间中的最大值最下值呢?用线段树啊,很常规的单点查询线段树,还没更新操作。


测试数据:

10 3
1 1
2 1
3 1
4 1
5 1
6 1
7 1
8 1
9 1


4 6
1 3
1 2
2 1


3 1
1 1
1 3


6 1
1 1
2 1
3 1
1 1
5 1


5 17
3 4
1 1
3 1
1 1


7 13
4 4
1 2
1 6
4 4
3 5
5 4


4 17
1 3
2 1
2 3


5 4
3 1
1 2
1 3
3 4


4 11
1 3
1 1
1 1


7 1
1 6
6 1


5 3
3 4
1 4
3 5


代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX 1100000
#define INF 2147483647
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1


struct node {
    int v, len, sum; //sum表示这个分支的距离最大值
    node *next;
} *head[MAX * 2], tree[MAX * 2];
int n, m, ptr, ans, left, right;
int dp[MAX], mmax[MAX * 4], mmin[MAX * 4];


void Initial() {

    ptr = ans = 0;
    memset(dp, 0, sizeof (dp));
    memset(head, NULL, sizeof (head));
}
inline int min(int a,int b) {

    return (a)<(b)?(a):(b);
}
inline int max(int a,int b) {

    return (a)>(b)?(a):(b);
}
void AddEdge(int a, int b, int c) {

    tree[ptr].v = b, tree[ptr].len = c, tree[ptr].sum = 0;
    tree[ptr].next = head[a], head[a] = &tree[ptr++];
}
void CountDist(int s, int pa) {
    //转换为以1为根的树后向下计算到叶子的最大距离
    node *p = head[s];
    while (p != NULL) {

        if (p->v != pa) {

            CountDist(p->v, s);
            dp[s] = max(dp[s], dp[p->v] + p->len);
            p->sum = dp[p->v] + p->len;
        }
        p = p->next;
    }
}

void Tree_DP(int s, int pa) {
    //更新s到pa分支的最远值,并计算dp[s]
    node *p = head[pa];
    int tpmax = 0, i, j;


    while (p != NULL) {
        //计算pa节点除s外的最大分支
        if (p->v != s)
            tpmax = max(tpmax, p->sum);
        p = p->next;
    }


    p = head[s];
    while (p != NULL) {
        //这次是向上更新,计算这个节点到其他分支的最大距离
        if (p->v == pa) {

            p->sum = tpmax + p->len;
            break;
        }
        p = p->next;
    }


    p = head[s];
    while (p != NULL) {
        //向下递归,并更新dp[s]
        dp[s] = max(dp[s], p->sum);
        if (p->v != pa) Tree_DP(p->v, s);
        p = p->next;
    }
}

void Push_Up(int rt) {

    mmax[rt] = max(mmax[rt << 1], mmax[rt << 1 | 1]);
    mmin[rt] = min(mmin[rt << 1], mmin[rt << 1 | 1]);
}

void Build_Tree(int l, int r, int rt) {

    if (l == r) {

        mmax[rt] = mmin[rt] = dp[l];
        return;
    }
    int m = (l + r) >> 1;
    Build_Tree(lson);
    Build_Tree(rson);
    Push_Up(rt);
}

int Query_Max(int L, int R, int l, int r, int rt) {
    //
    if (L <= l && r <= R) return mmax[rt];


    int m = (l + r) >> 1, tp1 = 0;
    if (L <= m) tp1 = max(tp1, Query_Max(L, R, lson));
    if (m + 1 <= R) tp1 = max(tp1, Query_Max(L, R, rson));
    return tp1;
}

int Query_Min(int L, int R, int l, int r, int rt) {

    if (L <= l && r <= R) return mmin[rt];


    int m = (l + r) >> 1, tp1 = INF;
    if (L <= m) tp1 = min(tp1, Query_Min(L, R, lson));
    if (m + 1 <= R) tp1 = min(tp1, Query_Min(L, R, rson));
    return tp1;
}

int main()
{
    int i, j, k, a, b, c;
    int tpmax, tpmin;


    while (scanf("%d%d", &n, &m) != EOF) {

        Initial();
        for (i = 2; i <= n; ++i) {

            scanf("%d%d", &b, &c);
            AddEdge(i, b, c), AddEdge(b, i, c);
        }


        CountDist(1, 0);
        Tree_DP(1, 0);


        Build_Tree(1, n, 1);
        left = 1, right = 1;
        tpmax = tpmin = dp[1];
        while (left <= right && right <= n) {

            if (tpmax - tpmin <= m) {

                ans = max(ans, right - left + 1);
                right++;
                tpmax = max(tpmax,dp[right]);
                tpmin = min(tpmin,dp[right]);
            }
            else {

                left++;
                tpmax = Query_Max(left, right, 1, n, 1);
                tpmin = Query_Min(left, right, 1, n, 1);
            }
            if (n - left < ans) break;
        }
        printf("%d\n", ans);
    }
}

本文ZeroClock原创,但可以转载,因为我们是兄弟。

你可能感兴趣的:(算法,struct,tree,null,query,Build)