这是一道用并查集维护,优先队列进行合并的题
题目链接(luogu)
CodeForces
给定 n n n 个点 m m m 条边的无向图,问是否能在图里添加 p p p 条边使得加边后存在恰好 q q q 个连通分量,同时请求出边权总和最小的方案。
新增边的两点若在同一个连通块则边权为1000,否则为 m i n ( 1 0 9 , S + 1 ) min(10^9, S+1) min(109,S+1) , 其中 S S S 为这条边连接的两个联通块的边权之和。
若存在方案,输出"YES"及任意一个边权和最小的方案;否则输出"NO"。
可以重边,但不可以自环。
首先考虑无解的情况:
1 。 1^。 1。 需要的边太多超过了 p p p
2 。 2^。 2。 连通块的数量少于 q q q
3 。 3^。 3。 还有边要加但是无边可加
接下来考虑如何维护最小的边权和:
我们考虑用一个优先队列,每次将边权和最小的两个块进行合并
最后如果还需要加边就随意加上加过的一条边构成重边即可
具体实现见代码:
const int N = 1e5 + 5;
const ll com = 1e9;
int n, m, p, q, tot, fa[N], tu, tv;
ll cost[N];
vector<pair<int, int> > Vec;
int findset(int x)
{
return x == fa[x] ? x : fa[x] = findset(fa[x]);
}
void link(int u, int v, int w)
{
u = findset(u), v = findset(v);
if (u == v)
cost[u] += w;
else
fa[u] = v, cost[v] += cost[u] + w;
}
priority_queue<pi, vector<pi>, greater<pi> > Que;
int main()
{
rd(n), rd(m), rd(p), rd(q);
for (int i = 1; i <= n; i++)
fa[i] = i;
for (int i = 1, x, y, z; i <= m; i++)
rd(x), rd(y), rd(z), link(x, y, z), tu = x, tv = y;
for (int i = 1; i <= n; i++)
if (findset(i) == i)
Que.push({cost[i], i}), tot++;
int res = tot - q;
if (res < 0 || res > p)
return !printf ("NO");
while (res--)
{
p--;
int t1 = findset(Que.top().se);
Que.pop();
int t2 = findset(Que.top().se);
Que.pop();
Vec.push_back({t1, t2});
tu = t1, tv = t2;
ll w = min(com, cost[t1] + cost[t2] + 1);
cost[t2] += cost[t1] + w;
Que.push({cost[t2], t2});
}
if (p && !tu)
return !printf ("NO");
while (p--)
Vec.push_back({tu, tv});
printf ("YES\n");
for (auto it : Vec)
printf ("%d %d\n", it.fi, it.se);
return 0;
}