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

Legend

Link \(\textrm{to LOJ}\)

Editorial

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

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

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

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

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


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

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

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

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

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


解决方法如下:

发现由于 \((i,j)\ (i < j)\) 的边权是 \(|i-j|\)\((i,i+1),(i+1,i+2),\cdots,(j-1,j)\) 的边权和也是 \(|j-i|\)

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

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

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

注意到可能成为最小生成树的边 \((l_1,r_1)\)\((l_2,r_2)\) 如果看成区间一定不会出现相交的情况。

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

于是对每一组询问均运行此算法:复杂度 \(O(n^2 \log n)\)

用桶代替排序,将并查集加上按秩合并和路径压缩,复杂度可以优化为 \(O(n^2 \alpha(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 @ 2021-01-12 09:21  Imakf  阅读(197)  评论(5编辑  收藏  举报