【题解】 「联合省选 2020 B」丁香之路 欧拉回路+最小生成树+贪心

Legend

Link to LOJ

Editorial

因为 siqi 哥哥出过一道欧拉回路的神仙题,所以说一下来就看出来是欧拉回路了呢!

如何判断无向图欧拉回路存在?每个点度数都是偶数且是连通图。

容易发现,题目即回答 q 组询问:增加边 (s,i) (1in) 后,再增加若干边,使得图存在欧拉回路。

并使得增加的边权尽量少。

注意到没有连边的点没必要考虑进来。


不难发现奇数度数的结点有偶数个,我们按大小两两贪心配对连接即可。

但这还不够,原因是可能图不联通。我们还需要把图连起来。

我们化用之前的方法,加上并查集,按编号贪心连接(获得 60分 WA)

为什么这个做法会错呢?我找到一组简易 HACK 数据:

s=1,i=5 时,按照上述算法会连出左图,正解是右图:


解决方法如下:

发现由于 (i,j) (i<j) 的边权是 |ij|(i,i+1),(i+1,i+2),,(j1,j) 的边权和也是 |ji|

所以新增一条 (i,j) 边等价于连接了 (i,i+1),(i+1,i+2),,(j1,j)

并且这第二种方式没有改变 i+1,i+2,,j1 的度数奇偶性,还能连接更多的联通块,这是非常好的。

最后剩余还没有连接的联通块,也就只能贪心最小生成树了。

注意到可能成为最小生成树的边 (l1,r1)(l2,r2) 如果看成区间一定不会出现相交的情况。

即最多只有 O(n) 条边参与生成树。

于是对每一组询问均运行此算法:复杂度 O(n2logn)

用桶代替排序,将并查集加上按秩合并和路径压缩,复杂度可以优化为 O(n2α(n))

Editorial

int n, m, s, d[MX], FF[MX], fa[MX], sz[MX] ,hav[MX] ,SZ[MX];
void init() {
    for (int i = 1 ; i < MX ; ++i)
        FF[i] = fa[i] = i;
}
int find1(int x) {return FF[x] == x ? x : FF[x] = find1(FF[x]);}
void link1(int x, int y) {x = find1(x), y = find1(y), FF[x] = y;}
int find(int x) {return fa[x] == x ? x : fa[x] = find(fa[x]);}
void link(int x, int y) {
    x = find(x), y = find(y);

    if (x == y) return;

    if (sz[x] < sz[y])
        std::swap(x, y);
    sz[x] += sz[y], fa[y] = x;
}

LL ans;

struct edge {
    int u, v, w;
    bool operator <(const edge &B)const {
        return w < B.w;
    }
} e[MX];

int main() {
	__FILE([省选联考2020B卷]丁香之路);
    init();
    n = read(), m = read(), s = read();

    for (int i = 1, u, v ; i <= m ; ++i) {
        u = read(), v = read();
		++hav[u] ,++hav[v];
        link1(u, v);
        ans += std::abs(u - v);
        d[u] ^= 1, d[v] ^= 1;
    }

    for (int i = 1 ; i <= n ; ++i) find1(i);

    memcpy(fa, FF, sizeof fa);
	memcpy(SZ ,sz ,sizeof sz);

	++hav[s];
	d[s] ^= 1;
    for (int i = 1 ; i <= n ; ++i) {
		debug("%d\n" ,i);
		memcpy(fa ,FF ,sizeof fa);
		memcpy(sz ,SZ ,sizeof sz);
        link(s, i);
		++hav[i];
        d[i] ^= 1;
        int tmp = 0;

        for (int j = 1, las = 0 ; j <= n ; ++j) {
            if (!d[j]) continue;
            if (las) {
                int fdj = find(j);
                tmp += j - las;
                link(fdj, las);

                for (++las ; las < j ; ++las) {
                    int k = find(las);
                    link(k, fdj);
                }
                las = 0;
            } else las = j;
        }

        d[i] ^= 1;
        int ecnt = 0;

        for (int j = 1, las = 0 ; j <= n ; ++j) {
            if (!hav[j]) continue;
            if (las) {
                if (find(j) != find(las)) {
                    e[++ecnt] = (edge) { j, las, (j - las) * 2 };
                }
            }
            las = j;
        }
        std::sort(e + 1, e + 1 + ecnt);

        for (int j = 1, u, v ; j <= ecnt ; ++j) {
            u = find(e[j].u), v = find(e[j].v);
            if (u == v) continue;
            tmp += e[j].w;
            link(u, v);
        }
		--hav[i];
        printf("%lld%c", ans + tmp, " \n"[i == n]);
    }

    return 0;
}
posted @   Imakf  阅读(220)  评论(5编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话
点击右上角即可分享
微信分享提示