题目大意:
可以一张\(n\)个节点\(m\)条边的无向图,对你进行\(k\)次询问,每次询问给你一个值\(x\),让你求出权值\(\displaystyle\sum_{i=1}^{n-1}|w_i-x|\)最小的最小生成树,输出所有询问结果的异或和
题目分析:
求最小生成树,我们首先会想到Kruskal算法来求解,但是询问次数特别多,所以每次询问用Kruskal求最小生成树肯定会超时
考虑一下Kruskal算法的过程,如果每次询问的值\(x\)比原来增加1,但是权值的大小顺序没有发生改变,那么最小生成树边的选择一定不会改变,接下来我们只需要思考一下,\(x\)什么时候会改变权值的大小顺序
假设有边\(i,j\)权值为\(w_i,w_j\),当\(x\le \frac{w_i+w_j}{2}\)的时候,\(|x-w_i|\)和\(|x-w_j|\)的大小顺序不发生改变;而当\(x\ge \frac{w_i+w_j}{2}+1\)的时候,权值的大小顺序发生改变,那么最小生成树就有重新选边的必要了。所以我们可以将\(\frac{w_i+w_j}{2}+1\)作为分界点,让其对应一个最小生成树的选边情况。
这样一来,我们先求出这张图中任意两条边的分界点,看作是最小生成树的一种情况,并将该情况的所有边权存储下来,时间复杂度为\(O(m^2)\),数据量最坏为\(90000\)。通过二分来确定询问所在的区间,通过存下来的边权的情况,求出询问的结果,最后得到答案
代码:
#include <bits/stdc++.h>
using namespace std;
#define _range(container) container.begin(), container.end()
#define L_B lower_bound
#define U_B upper_bound
constexpr int N = 1e5 + 100;
int n, m;
int cur[N], p[N], cnt;
LL result[N], res;
VI ed, order[N];
struct Edge {
int a, b, w;
} edge[N];
int _x;
inline bool cmp(Edge a, Edge b) {
return abs(_x-a.w) < abs(_x-b.w);
}
int find(int x) {
if (x != p[x]) p[x] = find(p[x]);
return p[x];
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 0; i < m; i++) {
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
edge[i] = {a, b, c};
}
for (int i = 1; i < m; i++)
for (int j = 0; j < i; j++) {
int mid = (edge[i].w + edge[j].w) / 2 + 1;
ed.emplace_back(mid);
}
// 特殊情况,当询问的x=0,那么问题就变成求最小生成树
ed.emplace_back(0);
sort(_range(ed));
int cnt = 0;
for (auto x : ed) {
_x = x;
sort(edge, edge + m, cmp);
for (int i = 0; i <= n + 1; i++) p[i] = i;
for (int i = 0; i < m; i++) {
int a = find(edge[i].a), b = find(edge[i].b), w = edge[i].w;
if (a != b) {
p[a] = b;
order[cnt].emplace_back(w);
}
}
cnt++;
}
int p, k, a, b, c, x;
scanf("%d%d%d%d%d", &p, &k, &a, &b, &c);
for (int i = 1; i <= k; i++) {
if (i <= p) scanf("%d", &x);
else x = (x * 1LL * a + b) % c;
int t = U_B(_range(ed), x) - ed.begin() - 1;
LL sum = 0;
for (auto e : order[t])
sum += abs(e - x);
res ^= sum;
}
printf("%lld\n", res);
return 0;
}