GDCPC 2024 F Graph Solution

GDCPC 2024 F Graph Solution

题面

此处

思路

当时在赛场上直接跳过了这一题。

后来发现只是个大模拟,后悔不已。

\(k=\lceil\frac{m}{n-1}\rceil\) 想到构造树来区分路径,加入边时使用二分查找最小的可合并森林编号。

为了这道题十分特殊的空间申请方法,我写了一份符合周礼的代码。

下面是解析。

解析

  1. 并查集

首先我们需要一个并查集。

class dsu
{
private:
    vi f;
    int n;
    int find(int x)
    {
        return x == f[x] ? x : f[x] = find(f[x]);
    }

public:
    dsu(int tn) : n(tn), f(tn + 1)
    {
        for (int i = 1; i <= tn; i++)
        {
            f[i] = i;
        }
    }
    bool conn(int x, int y)
    {
        return find(x) == find(y);
    }
    void merge(int x, int y)
    {
        if (conn(x, y))
            return;
        f[find(y)] = find(x);
    }
};

这个并查集提供了一个额外函数 conn(x,y),意义为判断并查集中 \(x\)\(y\) 点是否连通。

  1. 森林

其次我们需要一片森林。

class graph
{
private:
    int n;
    dsu ds;
    vector<vi> road;
    vi f;

public:
    graph(int tn) : n(tn), ds(tn), road(tn + 1, vi{}), f(tn + 1, 0)
    {
    }
    bool disc(int x, int y)
    {
        return !ds.conn(x, y);
    }
    void insert(int x, int y)
    {
        ds.merge(x, y);
        road[x].push_back(y);
        road[y].push_back(x);
    }
    void init(int x)
    {
        for (auto &i : road[x])
        {
            if (i == f[x])
                continue;
            f[i] = x;
            init(i);
        }
    }
    void build(int rt)
    {
        f[rt] = rt;
        init(rt);
    }
    vi extract(int x, int y)
    {
        vi ret;
        while (x != y)
            ret.push_back(x), x = f[x];
        ret.push_back(y);
        return ret;
    }
};

该图中提供的 disc(x,y) 函数通过调用并查集中的 conn(x,y) 函数实现,保证该图是一片森林。

extract(x,y) 表示提取树中从 \(x\)\(y\) 的路径,调用条件是 \(y\)\(x\) 的祖先。

  1. 森林集

接下来我们需要将这些森林组合起来。

class graphset
{
private:
    vector<graph> grph;

public:
    graphset(int n, int m) : grph((m + n - 2) / (n - 1), n) {}
    bool connect(int x, int y)
    {
        int l = 0, r = grph.size();
        while (l < r)
        {
            int mid = (l + r) >> 1;

            if (grph[mid].disc(x, y))
                r = mid;
            else
                l = mid + 1;
        }
        if (l < grph.size())
            grph[l].insert(x, y);
        return l >= grph.size() - 1;
    }
    void print(int x, int y)
    {
        printf("%d %d\n", x, y);
        for (auto &i : grph)
        {
            i.build(y);
            vi tmp = i.extract(x, y);
            printf("%d ", tmp.size());
            for (auto &j : tmp)
            {
                printf("%d ", j);
            }
            putchar('\n');
        }
    }
};

connect(x,y) 函数会在所有的 \(k=\lceil\frac{m}{n-1}\rceil\) 片森林中选择编号最小的可以进行合并的森林加入边,并在可以构造出答案时返回 true

进行上述准备工作后,核心代码的长度会相对较短。

void run()
{
    scanf("%d%d", &n, &m);
    graphset gph(n, m);
    rx = ry = -1;
    for (int i = 1, x, y; i <= m; i++)
    {
        scanf("%d%d", &x, &y);
        if (!gph.connect(x, y))
            continue;
        rx = x, ry = y;
    }
    if (rx == ry)
    {
        puts("-1");
        return;
    }
    gph.print(rx, ry);
}
int main()
{
    int T = 1;
    scanf("%d", &T);
    while (T--)
        run();
}

于是这道题就做完了。

posted @ 2024-06-01 15:45  丝羽绫华  阅读(58)  评论(0编辑  收藏  举报