【生成树,堆】【CF1095F】 Make It Connected
Description
给定 \(n\) 个点,每个点有点权,连结两个点花费的代价为两点的点权和。另外有 \(m\) 条特殊边,参数为 \(x,y,z\)。意为如果你选择这条边,就可以花费 \(z\) 的代价将点 \(x\) 和点 \(y\) 连结起来,当然你也可以不选择这条边。求使整个图联通的最小代价
Input
第一行是两个整数,分别是点数 \(n\) 和特殊边的数量 \(m\)
下面一行 \(n\) 个数,第 \(i\) 个数代表点 \(i\) 的点权
下面 \(m\) 行,每行三个数 \(x,y,z\),代表一条特殊边
Output
输出一行一个整数,最小代价
Hint
\(1~\leq~n~\leq~2~\times~10^5~,0~\leq~m~\leq~2~\times~10^5\)
设第 \(i\) 个点的点权为 \(a_i\),第 \(j\) 条特殊边的边权为 \(z_j\),保证
\(1~\leq~a_i,z_j~\leq~10^{12}\)
保证特殊边的参数 \(x~\neq~y\)
Solution
显然这个题目是要求一个 MST (最小生成树),但是 \(O(n^2)\) 建图显然不可取。
我们考虑克鲁斯卡尔算法的本质:
有若干个联通块,每次寻找联通块间权值最小的边将之连结。
考虑对于本题来说,在不考虑特殊边的情况下,连结两个联通块,显然要分别选择两个联通块内点权最小的点连结。于是我们对每个集合维护点权最小的点,每次取出点权前两小的集合进行连边即可。维护点权前两小的集合显然可以用一个堆做。
考虑有特殊边怎么办:
我们把特殊边排序,每次比较当前的特殊边的权值小还是前两个联通块的点权小,选择更小的合并。
注意处理一下当前选出的两个点在一个集合中的情况。
因为一共要做 \(O(n)\) 次,每次会有 \(O(1)\) 次对堆的插入和删除操作,于是复杂度 \(O(n \log n)\)
Code
#include <cstdio>
#include <queue>
#include <algorithm>
#ifdef ONLINE_JUDGE
#define freopen(a, b, c)
#endif
#define rg register
#define ci const int
#define cl const long long
typedef long long int ll;
namespace IPT {
const int L = 1000000;
char buf[L], *front=buf, *end=buf;
char GetChar() {
if (front == end) {
end = buf + fread(front = buf, 1, L, stdin);
if (front == end) return -1;
}
return *(front++);
}
}
template <typename T>
inline void qr(T &x) {
rg char ch = IPT::GetChar(), lst = ' ';
while ((ch > '9') || (ch < '0')) lst = ch, ch=IPT::GetChar();
while ((ch >= '0') && (ch <= '9')) x = (x << 1) + (x << 3) + (ch ^ 48), ch = IPT::GetChar();
if (lst == '-') x = -x;
}
template <typename T>
inline void ReadDb(T &x) {
rg char ch = IPT::GetChar(), lst = ' ';
while ((ch > '9') || (ch < '0')) lst = ch, ch = IPT::GetChar();
while ((ch >= '0') && (ch <= '9')) x = x * 10 + (ch ^ 48), ch = IPT::GetChar();
if (ch == '.') {
ch = IPT::GetChar();
double base = 1;
while ((ch >= '0') && (ch <= '9')) x += (ch ^ 48) * ((base *= 0.1)), ch = IPT::GetChar();
}
if (lst == '-') x = -x;
}
namespace OPT {
char buf[120];
}
template <typename T>
inline void qw(T x, const char aft, const bool pt) {
if (x < 0) {x = -x, putchar('-');}
rg int top=0;
do {OPT::buf[++top] = x % 10 + '0';} while (x /= 10);
while (top) putchar(OPT::buf[top--]);
if (pt) putchar(aft);
}
const int maxn = 200010;
const int maxm = 400010;
struct Edge {
int from, to;
ll v;
inline bool operator<(const Edge &_others) const {
return this->v < _others.v;
}
};
Edge edge[maxm];
int n, m;
int ufs[maxn], vec[maxn], rk[maxn];
ll ans;
ll MU[maxn];
struct Zay {
int id;
ll v;
inline bool operator<(const Zay &_others) const {
return this->v > _others.v;
}
};
std::priority_queue<Zay>Q;
int find(ci x) {return ufs[x] == x ? x : ufs[x] = find(ufs[x]);}
int main() {
freopen("1.in", "r", stdin);
qr(n); qr(m);
for (rg int i = 1; i <= n; ++i) qr(MU[i]);
for (rg int i = 1; i <= m; ++i) {
qr(edge[i].from); qr(edge[i].to); qr(edge[i].v);
}
std::sort(edge + 1, edge + 1 + m);
edge[m + 1].v = 1ll << 62;
for (rg int i = 1; i <= n; ++i) ufs[i] = i, vec[i] = i, rk[i] = 1, Q.push((Zay){i, MU[i]});
for (rg int i = 1, cur = 1; i < n; ++i) {
while ((cur <= m) && (find(edge[cur].from) == find(edge[cur].to))) ++cur;
Zay t1 = Q.top(); Q.pop(); Zay t2 = Q.top(); Q.pop();
while (find(t1.id) == find(t2.id)) {t2 = Q.top(); Q.pop();}
if ((t1.v + t2.v) <= edge[cur].v) {
int fa = find(t1.id), fb = find(t2.id);
ans += t1.v + t2.v;
if (rk[fa] < rk[fb]) ufs[fb] = fa;
else if (rk[fa] > rk[fb]) ufs[fa] = fb;
else ufs[fa] = fb, ++rk[fb];
Q.push((Zay){find(fa), t1.v});
vec[find(fa)] = vec[fa];
} else {
int fa = find(edge[cur].from), fb = find(edge[cur].to);
ans += edge[cur].v;
if (rk[fa] < rk[fb]) ufs[fb] = fa;
else if (rk[fa] > rk[fb]) ufs[fa] = fb;
else ufs[fa] = fb, ++rk[fb];
Q.push((Zay){find(fa), MU[vec[fa]]});
vec[find(fa)] = vec[fa];
Q.push(t1); Q.push(t2);
}
}
qw(ans, '\n', true);
return 0;
}