ZOJ 3649 Social Net

前言

这题鸽了有一段时间了,今天上午自己想了想,冒出了一个不错的思路,而且似乎和之前听做过的同学讲的不太一样。然后就开始敲了,编译完马上过样例。交上去WA,TLE,各种错误接连而至。一直到刚刚,才结束了半天多的Debug,发现是快读打挂了…(逃…)
这是一个极其惨痛的教训。

题意

给你有N个节点的树,每个点都有一个权值。有Q个询问,每次询问从一个点走到另一个点,问你走过节点的序列 c 1 , c 2 , . . . c k 中最大的 c i c j ,且 c i c j , j i

约定

2 N 3 × 10 4 , 1 q 3 × 10 4

分析

PART 1 转化

其实就是求一个序列中两个数一前一后的最大差值,注意到这个差值并非简单的绝对值,它是有顺序性的,大的必须在后面。
我们先不考虑树上的情况。对于一个线性的序列 C ,我 ubhynj们考虑它的差分序列 D ,则任意两数之差就是他们之间的差值求和。这样,我们对序列 C 要求的这个最大差值就转化成了序列 D 上的最大连续子段和。
这样,对于每次询问 Q ( u , v ) ,我们可以从 u 走到 l a c ( u , v ) 再走到 v ,并求出这段序列的最大连续子段和。

PART 2 提高效率

我们发现上面的查询每次都是 O ( N ) 的,那么整个算法就变成了 O ( Q N ) 的,这样显然会超时。我们需要提高查询的效率。
平时遇到树上这种查询的问题,我们一般用倍增法,压缩信息,达到降低复杂度的目的。那么对于这题是否可行呢?比如我们维护一个倍增数组 D P [ u ] [ i ] 表示从 u 开始到 u 的第 2 i 级父亲的最大连续子段和,但是我们会发现,我们合并的时候,这个最大连续子段和可能是独立在 u 到它的第 2 i 1 级父亲或者是它的第 2 i 1 级父亲到它的第 2 i 级父亲里的,也可能是这两段中的接在一起的,但是我们只维护这一个数组却丢失了这些信息。并且,如果 u v 不在同一条链上,那么我们走到 l c a ( u , v ) 还会拐一个弯,当我们从 l c a ( u , v ) 走到 v 的时候,方向和我们的倍增数组反过来了。
因此,看起来这个做法不可做
怎么可能呢?其实我们刚刚考虑合并的时候,已经找到了合并的正确方法。
我们还是来考虑一个线性序列 C = c 1 , c 2 , . . . , c n ,我们假设已经求得了 c 1 , c 2 , . . . , c i 以及 c i + 1 , c i + 2 , . . . c n 这两个子段的最大连续子段和,记这两个子序列为 C 1 , C 2 。对于每个序列我们维护四个信息:

  • s u m C :序列中所有数的和
  • l s u m C :以序列第一数为起始的最大连续子段和
  • r s u m C :以序列最后一个数为结尾的最大连续子段和
  • M a x S u m C :序列中的最大连续子段和

则我们可以得出:

  • s u m C = s u m C 1 + s u m C 2
  • l s u m C = m a x { l s u m C 1 , s u m C 1 + l s u m C 2 }
  • r s u m C = m a x { r s u m C 2 , s u m C 2 + r s u m C 1 }
  • M a x S u m C = m a x { M a x S u m C 1 , M a x S u m C 2 , r s u m C 1 + l s u m C 2 }

这些式子很容易就能理解。
那么对于树上倍增的做法也是类似,只是情况会更多一些。转移的式子很好推出,这里就不多展开了。并且注意,当从 l c a ( u , v ) 走到 v 的时候,方向是反的,因此我们维护这个 M a x S u m 的时候还要将我们对点和点差分得到的边权正负反一反即可。

参考程序

// vjudge 241954 I
#include <cstdio>
#include <cstring>
#include <algorithm>
const int MAXN = 30005;
const int Lg_N = 20;
const int MAXM = 50005;

struct Edge {
    int to, next;
} E[MAXN << 1];

int N, B[MAXN], M, Q, last[MAXN], tote, dep[MAXN], F[MAXN][Lg_N];

namespace MAX_Spanning_tree {
    struct Relation {
        int x, y, ai;
        bool operator<(const Relation & r) const {
            return ai > r.ai;
        }
    } R[MAXM];
    int fa[MAXN];
    inline int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); }
    int Kruskal();
}
// something wrong with this
// 这是莫名打挂的一个快读,就是因为这个东西调了一天,但是问题还没有找出来,不删去,放作警示
/* namespace FastIO {
    template <typename T>
    inline int read(T & x) {
        x = 0; register char ch = getchar();
        for (; ch != EOF && (ch < '0' || ch > '9'); ch = getchar());
        if (ch == EOF) return -1;
        for (; ch >= '0' && ch <= '9'; x = (x << 3) + (x << 1) + (ch ^ '0'), ch = getchar());
    }

    template <typename T>
    inline void write(T x) {
        if (!x) return (void)(putchar('0'));
        register int arr[20], len = 0;
        for (; x; arr[len++] = x % 10, x /= 10);
        while (len) putchar(arr[--len] ^ '0');
    }

    template <typename T>
    inline void writeln(T x) {
        write(x), putchar('\n');
    }
} */
inline void add_edge(int u, int v) {
    E[++tote].to = v, E[tote].next = last[u], last[u] = tote;
    E[++tote].to = u, E[tote].next = last[v], last[v] = tote;
}
void solve();
void dfs(int u);
int query(int u, int v);

int main() {
    while (scanf("%d", &N) == 1) solve();
    return 0;
}

void solve() {
    int i;
    for (i = 1; i <= N; i++) scanf("%d", &B[i]);
    scanf("%d", &M);
    {
        using MAX_Spanning_tree::R;
        for (i = 0; i < M; i++) scanf("%d%d%d", &R[i].x, &R[i].y, &R[i].ai);
    }
    printf("%d\n", MAX_Spanning_tree::Kruskal());   // 先要求一个最大生成树
    memset(F, 0, sizeof(F));
    dep[1] = 0, dfs(1);
    scanf("%d", &Q);
    int x, y;
    for (i = 0; i < Q; i++) {
        scanf("%d%d", &x, &y);
        printf("%d\n", query(x, y));
    }
}

namespace MAX_Spanning_tree {
    int Kruskal() {
        int i;
        tote = 0;
        memset(last, 0, sizeof(last));
        for (i = 1; i <= N; i++) fa[i] = i;
        std::sort(R, R + M);
        int cnt = 0, N1 = N - 1, fa1, fa2;
        int res = 0;
        for (i = 0; i < M; i++) {
            fa1 = find(R[i].x), fa2 = find(R[i].y);
            if (fa1 != fa2) {
                fa[fa1] = fa2;
                add_edge(R[i].x, R[i].y);
                res += R[i].ai;
                if (++cnt == N1) break;
            }
        }
        return res;
    }
}

int Al_sum[MAXN][Lg_N], Max_sum[MAXN][Lg_N][2][2], DP[MAXN][Lg_N][2];

void dfs(int u) {
    using std::max;
    int i, v;
    for (i = 1; i < Lg_N && F[u][i - 1]; i++)
        F[u][i] = F[F[u][i - 1]][i - 1],
        Al_sum[u][i] = Al_sum[u][i - 1] + Al_sum[F[u][i - 1]][i - 1],
        Max_sum[u][i][0][0] = max(Al_sum[u][i], max(Max_sum[u][i - 1][0][0], Al_sum[u][i - 1] + Max_sum[F[u][i - 1]][i - 1][0][0])),
        Max_sum[u][i][0][1] = max(Al_sum[u][i], max(Max_sum[u][i - 1][0][1] + Al_sum[F[u][i - 1]][i - 1], Max_sum[F[u][i - 1]][i - 1][0][1])),
        Max_sum[u][i][1][0] = max(-Al_sum[u][i], max(Max_sum[u][i - 1][1][0], Max_sum[F[u][i - 1]][i - 1][1][0] - Al_sum[u][i - 1])),
        Max_sum[u][i][1][1] = max(-Al_sum[u][i], max(Max_sum[u][i - 1][1][1] - Al_sum[F[u][i - 1]][i - 1], Max_sum[F[u][i - 1]][i - 1][1][1])),
        DP[u][i][0] = max(max(DP[u][i - 1][0], DP[F[u][i - 1]][i - 1][0]), Max_sum[u][i - 1][0][1] + Max_sum[F[u][i - 1]][i - 1][0][0]),
        DP[u][i][1] = max(max(DP[u][i - 1][1], DP[F[u][i - 1]][i - 1][1]), Max_sum[u][i - 1][1][1] + Max_sum[F[u][i - 1]][i - 1][1][0]);
    for (i = last[u]; i; i = E[i].next)
        if (E[i].to != F[u][0]) {
            dep[v = E[i].to] = dep[u] + 1, F[v][0] = u, 
            DP[v][0][0] = Max_sum[v][0][0][0] = Max_sum[v][0][0][1] = Al_sum[v][0] = B[u] - B[v],
            DP[v][0][1] = Max_sum[v][0][1][0] = Max_sum[v][0][1][1] = -Al_sum[v][0];
            dfs(v = E[i].to);
        }
}

int query(int u, int v) {
    using std::max;
    int i, delta, res = 0, max1 = 0, max2 = 0;
    if (dep[u] > dep[v]) {
        for (i = 0, delta = dep[u] - dep[v]; i < Lg_N && 1 << i <= delta; i++)
            if (delta >> i & 1) {   // 询问时维护答案,类似维护倍增数组,不过需要记录的信息少一些
                res = max(max(res, DP[u][i][0]), max1 + Max_sum[u][i][0][0]);
                max1 = max(Max_sum[u][i][0][1], Al_sum[u][i] + max1);
                u = F[u][i];
            }
    }
    else {
        for (i = 0, delta = dep[v] - dep[u]; i < Lg_N && 1 << i <= delta; i++)
            if (delta >> i & 1) {
                res = max(max(res, DP[v][i][1]), max2 + Max_sum[v][i][1][0]);
                max2 = max(Max_sum[v][i][1][1], max2 - Al_sum[v][i]);
                v = F[v][i];
            }
    }
    if (u == v) return max(res, max1 + max2);
    for (i = Lg_N - 1; i >= 0; i--)
        if (F[u][i] != F[v][i])
            res = max(max(res, max(DP[u][i][0], DP[v][i][1])), max(max1 + Max_sum[u][i][0][0], max2 + Max_sum[v][i][1][0])),
            max1 = max(Max_sum[u][i][0][1], max1 + Al_sum[u][i]),
            max2 = max(Max_sum[v][i][1][1], max2 - Al_sum[v][i]),
            u = F[u][i], v = F[v][i];
    res = max(max(res, max(DP[u][0][0], DP[v][0][1])), max(max1 + Max_sum[u][0][0][0], max2 + Max_sum[v][0][1][0])),
    max1 = max(Max_sum[u][0][0][1], max1 + Al_sum[u][0]),
    max2 = max(Max_sum[v][0][1][1], max2 - Al_sum[v][0]);
    return max(res, max1 + max2);
}

总结

这道题目,其实分析起来思维难度不大,代码认认真真仔细码,也不需要调太久。就是这个快读,嗯…我也不知道为什么,慎用!慎用!

猜你喜欢

转载自blog.csdn.net/HelloHitler/article/details/81393138
ZOJ