SenseTime Ace Coder Challenge 暨 商汤在线编程挑战赛* G题 危险路径

题目链接.

弱鸡选手表示只会做这种水题了。

首先mst。

之后问题转化为在一棵树上,求每个点到其它点的路径的最大值的和。

当然可以点剖什么的,也可以换根+线段树合并什么的,常数有点小炸。

我的想法是用启发式并查集。

mst要合并两个并查集时,设它们的根为x,y

siz[x] > siz[y], 将把y的父亲设为x。

显然未合并前的x的整个子树答案+z(当前边权)*siz[y],而y的子树答案+z*siz[x]。

处理一下x的标记,y的标记减去x的标记再加上自己应有的那一部分。

这样的话,最后就按照并查集的顺序从上到下遍历一遍传个标记就好了。

时间复杂度 O((n+m) log n)

Code:

#include
#include
#include
#define ll long long
#define ul unsigned long long
#define fo(i, x, y) for(ll i = x; i <= y; i ++)
using namespace std;

const ll N = 1e6 + 5;

ll T, n, m, x, y;
ll f[N];
ll sum[N], ans, siz[N];

ll find(ll x) {return f[x] == x ? x : find(f[x]);}

struct edge {
    ll x, y, z;
} e[N];

bool cmp(edge a, edge b) {
    return a.z < b.z;
}

ll final[N], to[N], nxt[N], tot;

void link(ll x, ll y) {
    nxt[++ tot] = final[x], to[tot] = y, final[x] = tot;
}

ll d[N];

int main() {
    ll num = 1;
    for(scanf("%lld", &T); T; T --, num ++) {
        scanf("%lld %lld", &n, &m);
        fo(i, 1, n) f[i] = i, siz[i] = 1, sum[i] = 0, final[i] = 0;
        fo(i, 1, tot) nxt[i] = to[i] = 0; tot = 0;
        fo(i, 1, m) scanf("%lld %lld %lld", &e[i].x, &e[i].y, &e[i].z);
        sort(e + 1, e + m + 1, cmp);
        fo(i, 1, m) {
            ll x = e[i].x, y = e[i].y, z = e[i].z;
            if(find(x) != find(y)) {
                x = find(x), y = find(y);
                if(siz[x] < siz[y]) swap(x, y);
                sum[x] += z * siz[y];
                sum[y] -= sum[x] -  z * siz[x];
                f[y] = x; siz[x] += siz[y];
                link(x, y);
            }
        }
        ll g = find(1); ul ss = 0;
        d[d[0] = 1] = g;
        fo(i, 1, d[0]) {
            ll x = d[i]; ss ^= (ul) sum[x] * x;
            for(ll j = final[x]; j; j = nxt[j])
                sum[to[j]] += sum[x], d[++ d[0]] = to[j];
        }
        printf("Case #%lld: %llu\n", num, ss);
    }
}

你可能感兴趣的:(信息学,启发式并查集,并查集,启发式算法)