F. Phoenix and Earthquake(图论构造)
题目传送门
题面:
题目大意:
给你n个点,m条边,限制x,每个点都有沥青a[i],定义合并两个点即两点之间有边且a[u]+ a[v] ≥ x
,合并之后的沥青为a[u]+ a[v] -x
,现在让你输出一种选选边方式,选n − 1条边使得n个点联通。
操作:每次都选最大的a[i],之后将他与跟他相连的且不在他集合中的点合并。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e6 + 10;
ll f[maxn];
ll a[maxn];
ll find(ll x) {
return (x == f[x]) ? x : f[x] = find(f[x]);
//并查集
}
int main() {
priority_queue<pair<ll, ll>> pq;
//优先队列存的是权+编号。
vector<vector<pair<ll, ll>>> v(maxn);
//向量存的是v[u]={v,i},(u,v)边是i编号的边。(i=1~m)
pair<ll, ll> tmp;
ll n, m, x;
ll sum = 0;
cin >> n >> m >> x;
for (ll i = 1; i <= n; i++) {
cin >> a[i];
sum += a[i];
pq.push({a[i], i});
f[i] = i;
//并查集初始化
}
for (ll i = 1; i <= m; i++) {
ll a, b;
cin >> a >> b;
v[a].push_back({b, i});
v[b].push_back({a, i});
}
if (1ll * (n * x - x) > sum) {
printf("No\n");
//这种情况肯定无解
} else {
printf("Yes\n");
for (ll i = 1; i <= n - 1; i++) {
tmp = pq.top();
pq.pop();
ll t = tmp.second;
//尽量找a[i]最大的点——tmp。
//t为点tmp的编号
while (t != find(t)) {
tmp = pq.top();
pq.pop();
t = tmp.second;
//如果t已经被别的合并过去了,就跳过一直找。
}
ll sn = find(v[t].back().first);
//sn是下一个合并的点集合的代表点;
while ((t == sn) && v[t].size())
//v[t]相连的点和他已经合并过,即该需要连接点所在集合的代表就是t自己,那么就跳过。
{
v[t].pop_back();
sn = find(v[t].back().first);
}
///操作:每次都选最大的a[i],之后将他与跟他相连的且不在他集合中的点合并
printf("%d\n", v[t].back().second);
a[t] += a[sn] - x;
tmp.first = a[t] + a[sn] - x;
pq.push(tmp);
f[sn] = t;
//将sn并入集合
if (v[t].size() < v[sn].size()) {
swap(v[t], v[sn]);
//我们把t视作代表点。
}
v[t].insert(v[t].end(), v[sn].begin(), v[sn].end());
v[sn].clear();
}
}
}
遗留问题:
///下面这步我是真看不太明白...
if (v[t].size() < v[sn].size()) {
swap(v[t], v[sn]);
//我们把t视作代表点。
}
因为少这一步,一直WA10…