ZOJ 3649 Social Net(树上倍增)

一整天基本上只打了两题,每题半天,调起来简直爽爆。
There are n individuals(2 <= n <= 30000). Everyone has one or more friends. And everyone can contact all people by friend-relation. If two persons aren’t friends, they also can contact by their friends. Each pair of friends have a friendship value ai(1 <= ai <= 50000).

Firstly, you will relieve some friend-relation. The rest of the friend-relation is the social net. The net is unique in all test cases. In this net, everyone can contact all people by rest friend-relation. The net has a minimum number of friend-relation. And the net has maximum sum of friendship value. We want to get the maximum sum.

Secondly, everyone has an angry value bi(1 <= bi <= 100000). We have q operations(1 <= q <= 30000): Person X wants to contact person Y, this operation merely has one sequence which describes the process. The sequence consists of persons’ angry value. The persons are on the process.

We suppose the sequence is c1, c2, c3, … ,ci. Here ci means the angry value of the ith people in the sequence.

We attempt to find the maximum ck-cj (ck >= cj, j <= k).

Example:

The sequence is 3(X), 4, 5, 6, 7, 5, 9, 4, 11(Y). The maximum ck-cj is 11-3=8.

The sequence is 3(X), 4, 5, 6, 7, 5, 9, 2, 11(Y). The maximum ck-cj is 11-2=9.

The sequence is 3(X), 10, 2, 5(Y). The maximum ck-cj is 10-3=7.

Input
The input contains multiple test cases. Each test case begins with a line containing a single integer n. The following line contains n integers bi.

The subsequent line describe the number of relations m(n <= m <= 50000). The next m lines contain the information about relations: x, y, ai. Their friendship value is ai.

Afterward gives q. The next q lines contain the operations: x, y. person X wants to contact person Y.

Output
For each case, print maximum sum of friendship value of the net on the first line.

The next q lines contain the answers of every operations.

Sample Input
6
3 5 1 7 3 5
7
1 2 5
1 3 6
2 4 7
2 5 8
3 6 9
4 5 1
5 6 2
5
6 1
6 2
6 3
6 4
6 5
Sample Output
35
2
4
0
6
4

题意:

首先求一遍最大生成树,然后有一些询问,求X到Y的路径上满足(ck >= cj, j <= k)的最大的ck-cj,c是点的权值。

思路:

  • 最大生成树就是模板

  • 求路径上的差值,最简单的想法就是倍增同时记录一下路径上的最大最小值。然而这个差值是有方向的,而倍增单纯地求最大值和最小值的话,无法判断方向。于是有一种我也不知道到底是自己想出来还是听老师讲才知道的方法,维护好多好多倍增数组,解决方向的问题。

  • 首先我们需要5个数组:1f[][]最简单的父亲节点编号,2maxd[][]路径最大值,3mind[][]路径最小值,4maxup[][]从当前节点向上走的最大差值,5maxdown[][]从上面节点向下走到当前节点的最大差值。求这些数组的值一遍DFS就行了,最后回答query时,只要把X到LCA的最大差值,LCA到Y的最大差值,还有Y到LCA的路径最大值减X到LCA的路径最小值的差值取max就好了。

  • 具体求解还是比较麻烦的,主要是思路不清晰,尽管看起来是已经知道标算了。

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const ll INF = 1e18+10;
const ll N = 30010;
const ll M = 50010;
const ll E = 18;
ll n, m, ang[N];
//MST
struct PREEDGE{
    ll u, v, w;
}preedge[M];
ll fa[N], sum;
//operate
struct EDGE{
    ll nxt, v;
}edge[N*2];
ll point[N], e;
ll q, dpt[N], f[N][E+1], maxd[N][E+1], mind[N][E+1], maxup[N][E+1], maxdown[N][E+1];

//read
void init()
{
    for (ll i = 1; i <= n; i++)
        scanf("%lld", &ang[i]);
    scanf("%lld", &m);
    for (ll i = 1; i <= m; i++)
        scanf("%lld%lld%lld", &preedge[i].u, &preedge[i].v, &preedge[i].w);
}

//MST
bool cmp(PREEDGE x, PREEDGE y){return x.w > y.w;}

ll FIND(ll u)
{
    if (fa[u] == u) return u;
    return fa[u] = FIND(fa[u]);
}

void add_edge(ll u, ll v)
{
    edge[++e] = (EDGE){point[u], v};
    point[u] = e;
}

void KRUSKAL()
{
    sort(preedge+1, preedge+m+1, cmp);
    for (ll i = 1; i <= n; i++) fa[i] = i;
    sum = 0;
    memset(point, -1, sizeof(point)); e = 0;
    for (ll i = 1; i <= m; i++){
        ll u = preedge[i].u;
        ll v = preedge[i].v;
        ll w = preedge[i].w;
        ll fau = FIND(u);
        ll fav = FIND(v);
        if (fau != fav){
            sum += w;
            fa[fav] = fau;
            add_edge(u, v);
            add_edge(v, u);
        }
    }
    printf("%lld\n", sum);
}

//operate
void DFS(ll u, ll fa)//求所有倍增数组
{
    for (int i = 1; i <= 18; i++){
        f[u][i] = f[f[u][i-1]][i-1];
        maxd[u][i] = max(maxd[u][i-1], maxd[f[u][i-1]][i-1]);
        mind[u][i] = min(mind[u][i-1], mind[f[u][i-1]][i-1]);
        maxup[u][i] = max(max(maxup[u][i-1], maxup[f[u][i-1]][i-1]), maxd[f[u][i-1]][i-1]-mind[u][i-1]);
        //maxup由两个较小的maxup,和较浅的区间最大值和较深的区间最大值相减取max得到,maxdown同理
        maxdown[u][i] = max(max(maxdown[u][i-1], maxdown[f[u][i-1]][i-1]), maxd[u][i-1]-mind[f[u][i-1]][i-1]);
    }
    for (int i = point[u]; i != -1; i = edge[i].nxt){
        ll v = edge[i].v;
        if (v == fa) continue;
        dpt[v] = dpt[u]+1;
        f[v][0] = u;
        maxd[v][0] = max(ang[u], ang[v]);
        //maxd可以包含自身,因为题目中的j可以等于k,即两个区间有一个元素重合也没关系
        mind[v][0] = min(ang[u], ang[v]);
        maxup[v][0] = max(0ll, ang[u]-ang[v]);
        maxdown[v][0] = max(0ll, ang[v]-ang[u]);
        DFS(v, u);
    }
}

// ll LCA1(ll x, ll y)原来的错误代码,对于一条路上的最大差值求法没有理解
// {
//     ll maxang = -INF, minx = INF, maxy = -INF;
//     for (ll i = 18; i >= 0; i--)
//         if (dpt[x]-(1<<i) >= dpt[y]){
//             maxang = max(maxang, max(maxup[x][i]), );
//             minx = min(minx, mind[x][i]);
//             x = f[x][i];
//         }
//     for (ll i = 18; i >= 0; i--)
//         if (f[x][i] != f[y][i]){
//             maxang = max(maxang, maxup[x][i]);
//             maxang = max(maxang, maxdown[y][i]);
//             minx = min(minx, mind[x][i]);
//             maxy = max(maxy, maxd[y][i]);
//             x = f[x][i];
//             y = f[y][i];
//         }
//     if (x != y){
//         maxang = max(maxang, maxup[x][0]);
//         maxang = max(maxang, maxdown[y][0]);
//         minx = min(minx, mind[x][0]);
//         maxy = max(maxy, maxd[y][0]);
//         x = f[x][0];
//         y = f[y][0];
//     }
//     return max(maxang, maxy-minx);
// }

ll LCA(ll x, ll y)//单纯地求最近公共祖先
{
    if (dpt[x] < dpt[y]) swap(x, y);
    for (ll i = 18; i >= 0; i--)
        if (dpt[x]-(1<<i) >= dpt[y])
            x = f[x][i];
    for (ll i = 18; i >= 0; i--)
        if (f[x][i] != f[y][i])
            x = f[x][i], y = f[y][i];
    if (x != y) x = f[x][0];
    return x;
}

ll GET_MAX(ll x, ll rt)//取一条向上的路上的最大值
{
    ll mmax = ang[x];
    for (ll i = 18; i >= 0; i--)
        if (dpt[x]-(1<<i) >= dpt[rt])
            mmax = max(mmax, maxd[x][i]), x = f[x][i];

    return mmax;
}

ll GET_MIN(ll x, ll rt)//取一条向上的路最小值
{
    ll mmin = ang[x];
    for (ll i = 18; i >= 0; i--)
        if (dpt[x]-(1<<i) >= dpt[rt])
            mmin = min(mmin, mind[x][i]), x = f[x][i];
    return mmin;
}

ll GET_UP(ll x, ll rt)//起床
{
    ll mmaxup = 0, mmin = INF;
    for (ll i = 18; i >= 0; i--)
        if (dpt[x]-(1<<i) >= dpt[rt]){
            mmaxup = max(mmaxup, max(maxup[x][i], maxd[x][i]-mmin));
            mmin = min(mmin, mind[x][i]);
            x = f[x][i];
        }
    return mmaxup;
}

ll GET_DOWN(ll x, ll rt)//求向下的路上的最大差值,求法和倍增初始化差不多
{
    ll mmaxdown = 0, mmax = -INF;
    for (ll i = 18; i >= 0; i--)
        if (dpt[x]-(1<<i) >= dpt[rt]){
            mmaxdown = max(mmaxdown, max(maxdown[x][i], mmax-mind[x][i]));
            mmax = max(mmax, maxd[x][i]);
            x = f[x][i];
        }
    return mmaxdown;
}

void OPERATE()
{
    memset(f, 0, sizeof(f));
    memset(maxd, 0, sizeof(maxd));
    memset(mind, 0x3f, sizeof(mind));
    memset(maxup, 0, sizeof(maxup));
    memset(maxdown, 0, sizeof(maxdown));
    dpt[1] = 0;
    DFS(1, 0);
    scanf("%lld", &q);
    while (q--){
        ll x, y, z;
        scanf("%lld%lld", &x, &y);
        z = LCA(x, y);
        // printf("%lld %lld %lld %lld %lld\n", z, GET_MAX(y, z), GET_MIN(x, z), GET_UP(x, z), GET_DOWN(y, z));
        printf("%lld\n", max(GET_MAX(y, z)-GET_MIN(x, z), max(GET_UP(x, z), GET_DOWN(y, z))));
    }
}

int main()//一写长点的程序主程序就会变得奇怪
{
    while (scanf("%lld", &n) != EOF){
        init();
        KRUSKAL();
        OPERATE();
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/xyyxyyx/article/details/81393873
ZOJ