ZOJ 3649 Social Net

本文介绍了一种解决树上节点最大差值查询问题的方法。通过将问题转化为最大连续子段和问题,利用倍增法优化查询效率。文章详细解释了解决方案的思路和实现细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

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

题意

给你有N个节点的树,每个点都有一个权值。有Q个询问,每次询问从一个点走到另一个点,问你走过节点的序列c1,c2,...ckc1,c2,...ck中最大的cicjci−cj,且cicj,jici≥cj,j≤i

约定

2N3×104,1q3×1042≤N≤3×104,1≤q≤3×104

分析

PART 1 转化

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

PART 2 提高效率

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

  • sumCsumC:序列中所有数的和
  • lsumClsumC:以序列第一数为起始的最大连续子段和
  • rsumCrsumC:以序列最后一个数为结尾的最大连续子段和
  • MaxSumCMaxSumC:序列中的最大连续子段和

则我们可以得出:

  • sumC=sumC1+sumC2sumC=sumC1+sumC2
  • lsumC=max{lsumC1,sumC1+lsumC2}lsumC=max{lsumC1,sumC1+lsumC2}
  • rsumC=max{rsumC2,sumC2+rsumC1}rsumC=max{rsumC2,sumC2+rsumC1}
  • MaxSumC=max{MaxSumC1,MaxSumC2,rsumC1+lsumC2}MaxSumC=max{MaxSumC1,MaxSumC2,rsumC1+lsumC2}

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

参考程序

// 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);
}

总结

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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值