CF362D Fools and Foolproof Road Solutions

这是一道用并查集维护,优先队列进行合并的题
题目链接(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;
}

Thanks!

你可能感兴趣的:(c++)