题目链接.
弱鸡选手表示只会做这种水题了。
首先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);
}
}