Loading

【题解】P3623 [APIO2008]免费道路

题意

P3623 [APIO2008]免费道路

给出一个包含 \(n\) 个顶点和 \(m\) 条无向边的无向图。已知边有 \(0\)\(1\) 两种类型,试求原图的一棵生成树,使得其恰好包含 \(k\) 条类型 \(0\) 的边。若不存在满足条件的生成树则输出 no solution

\(1 \leq n \leq 2 \times 10^4, 1 \leq m \leq 10^5, 0 \leq k \leq n - 1\)

思路

最小生成树。

对于连通的图,可以考虑从其中删去若干条边,构造出一棵符合条件的生成树。

假设图中仅剩类型为 \(1\) 的边,则此时图可能不连通。因此我们发现如果一条类型为 \(0\) 的边连接了两个当前尚未连通的顶点,说明当前边应该被加入图中。

当然,加入图中的边集 \(A\) 不唯一。为了使加入图中的边数量最少,这里可以用类似 kruskal 的方法求这些边。

图中仅剩类型为 \(0\) 的边的情况同理,令此时加入图中的边集为 \(B\)

求一棵生成树,实际上等价于希望图中仅剩 \(A\)\(B\) 时令图连通需要加入的边数最小。不妨令图中只剩下 \(A\)\(B\) 中的边,然后对原图跑一遍类似 kruskal 的算法,求出令图连通需要加入的边。

但是我们需要满足类型 \(0\) 的数量限制。因此我们可以考虑先对于类型 \(0\) 中不属于 \(A\) 的边求一部分最小生成树,把 \(k\) 条类型 \(0\) 的边加满;然后对于类型 \(1\) 中不属于 \(B\) 的边求剩下部分的最小生成树,加满类型 \(1\)\(n - 1 - k\) 条边。

容易看出跑最小生成树时遍历边的顺序不影响图的连通性,或者可以看作是给每条边赋上一个附加的权值,使得按照附加权值排序以后边按照我们遍历的顺序排列。

无解的情况为:

  1. 图不连通

  2. \(|A| > k\)\(|B| > n - 1 - k\)
    原因:上面令 \(A, B\) 的大小取到了可能的最小值,即 \(|A|, |B|\) 分别小于等于图中类型 \(0\) 和类型 \(1\) 的边的数量下限。如果仍然存在大于的情况说明无解。

  3. 最终加不满 \(k\) 条类型 \(0\) 的边或加不满 \(n - 1 - k\) 条类型 \(1\) 的边。
    原因:

    若加不满 \(k\) 条类型 \(0\) 的边,此时有两种可能:
    1. 图中类型 \(0\) 的边数量不足 \(k\) 条。
    2. 未加入图的边连接的两个顶点已经被 \(A, B\) 以及之后加入的类型 \(0\) 的边连通了。这意味着对于任意一种 \(A, B\) 的选取方式,类型 \(0\) 中存在恒定数量的“废边”。
    换言之,加入这些废边会导致出现若干环。此时在环中删去一条边 \((u, v)\),得到新的图 \(G^{\prime}\),此时可以看作是向 \(G^{\prime}\) 中加入边 \((u, v)\)。又因为加入 \((u, v)\) 会出现环,所以 \((u, v)\) 可以看作是该局面下的废边,即废边数量恒定。在此局面无法加满 \(k\) 条边,意味着在所有可能的局面下都无法加满 \(k\) 条边。
    不能加入废边的原因是生成树只有 \(n - 1\) 条边,加入废边会导致无法加入原本对连通性有贡献的边。

    若加不满 \(n - 1 - k\) 条类型 \(1\) 的边,此时又有两种可能:
    1. 图中类型 \(1\) 的边不足 \(n - 1 - k\)
    2. 原图不连通
    加满 \(k\) 条类型 \(0\) 的边时,假设此时使用了 \(x\) 条边,因为图中无环(不存在一条边连接两个已经连通的顶点),所以有 \(x + 1\) 个顶点被连通。不妨令所有连通块缩成一个点,构造出新图 \(G^{\prime}\),则我们需要在该图中用 \(n - 1 - x\) 条边连通 \(n - (x + 1) = n - x\) 个顶点(每加入一条边会合并两个连通块,即减少一个,共减少 \(x\) 个)。若新图连通,则显然有解。新图不连通的情况可以对应到原图不连通的情况。

综上,跑三遍 kruskal 即可。

时间复杂度 \(O(m \log n)\)

代码

#include <cstdio>
#include <vector>
using namespace std;

const int maxn = 2e4 + 5;
const int maxm = 1e5 + 5;

int n, m, k;
int fa[maxn];
int u[maxm], v[maxm], c[maxm];
vector<int> g[2];
bool vis[maxm];

void init()
{
    for (int i = 1; i <= n; i++) fa[i] = i;
}

int get(int x)
{
    if (fa[x] == x) return x;
    return fa[x] = get(fa[x]);
}

void merge(int x, int y)
{
    x = get(x);
    y = get(y);
    if (x != y) fa[y] = x;
}

bool check()
{
    int cnt = 0;
    for (int i = 1; i <= n; i++)
        if (get(i) == i) cnt++;
    return (cnt == 1);
}

int main()
{
    int cnt0, cnt1;
    cnt0 = cnt1 = 0;
    scanf("%d%d%d", &n, &m, &k);
    init();
    for (int i = 1; i <= m; i++)
    {
        scanf("%d%d%d", &u[i], &v[i], &c[i]);
        g[c[i]].push_back(i);
        merge(u[i], v[i]);
    }
    if (!check())
    {
        puts("no solution");
        return 0;
    }
    init();
    for (int i = 0; i < g[1].size(); i++) merge(u[g[1][i]], v[g[1][i]]);
    for (int i = 0; i < g[0].size(); i++)
    {
        if (get(u[g[0][i]]) != get(v[g[0][i]]))
        {
            merge(u[g[0][i]], v[g[0][i]]);
            vis[g[0][i]] = true;
            cnt0++;
        }
    }
    if (cnt0 > k)
    {
        puts("no solution");
        return 0;
    }
    init();
    for (int i = 0; i < g[0].size(); i++) merge(u[g[0][i]], v[g[0][i]]);
    for (int i = 0; i < g[1].size(); i++)
    {
        if (get(u[g[1][i]]) != get(v[g[1][i]]))
        {
            merge(u[g[1][i]], v[g[1][i]]);
            vis[g[1][i]] = true;
            cnt1++;
        }
    }
    if (cnt1 > n - 1 - k)
    {
        puts("no solution");
        return 0;
    }
    init();
    for (int i = 1; i <= m; i++)
        if (vis[i]) merge(u[i], v[i]);
    for (int i = 0; i < g[0].size(); i++)
    {
        if (cnt0 == k) break;
        if (get(u[g[0][i]]) != get(v[g[0][i]]))
        {
            merge(u[g[0][i]], v[g[0][i]]);
            vis[g[0][i]] = true;
            cnt0++;
        }
    }
    if (cnt0 < k)
    {
        puts("no solution");
        return 0;
    }
    for (int i = 0; i < g[1].size(); i++)
    {
        if (cnt1 == n - 1 - k) break;
        if (get(u[g[1][i]]) != get(v[g[1][i]]))
        {
            merge(u[g[1][i]], v[g[1][i]]);
            vis[g[1][i]] = true;
            cnt1++;
        }
    }
    for (int i = 1; i <= m; i++)
        if (vis[i]) printf("%d %d %d\n", u[i], v[i], c[i]);
    return 0;
}
posted @ 2022-03-12 17:24  kymru  阅读(50)  评论(0编辑  收藏  举报