E. Journey

E. Journey

Problem - E - Codeforces

\(kruskal\)重构树, 欧拉路径

首先不考虑操作二,那么题目就是问走过所有边回到 \(1\) 的最短路径,如果均仅走过一次,那么整个路径构成欧拉回路,答案为 \(\sum_i w_i\) ,否则,将有一些边走过多次。

那么操作二可以看作是在原图上建立一些虚拟边,避免走过重复边,缩小代价,答案就是虚拟边和原有的代价之和。

根据欧拉回路的定义,这些虚拟边根据每个点的度来建立,每两个度数为奇数的点(下文称为奇点)间可以建立

虚拟边的代价是两点之间路径编号最大的边的边权,且不要求简单路径。很容易想到并查集维护连通性即可确定虚拟边的代价,但复杂度较高。

考虑\(kruskal\)重构树,以原图上每个点作为叶子节点,按序号从小到大枚举边,将边作为非叶子节点,链接原图上非叶子的两个节点的祖先。

则两个奇点 \(u\) , \(v\) 间的虚拟边的代价即为重构树上两个点 \(u\) , \(v\) 的公共祖先的最小权值,可以\(O(n+m)\)计算完

class DSU{
public:
    vector<int> p;
    DSU(){}
    DSU(int n): p(n){iota(p.begin(), p.end(), 0);};
    int find(int x) {return x == p[x] ? x : p[x] = find(p[x]);}
};

void bluket() {
    int n = R, m = R;
    ll ans = 0;
    vector<ll> a(m + 1), deg(n+1);
    vector<vector<int>> adj(n + m + 1);
    DSU ds(n + m + 1);

    for (int i = 1; i <= m; i++) {
        int u = R, v = R, w = R;
        int fu = ds.find(u), fv = ds.find(v);
        deg[u]++, deg[v]++;
        ans += w;
        a[i] = w;

        // kurskal 重构树, 按节点编号合并
        if(fu == fv) {
            adj[i + n].push_back(fu);
            ds.p[fu] = i + n;
        }
        else {
            adj[i + n].push_back(fu);
            ds.p[fu] = i + n;

            adj[i + n].push_back(fv);
            ds.p[fv] = i + n;
        }
    }

    // u 当前节点,fw 祖宗节点到当前节点可以采用的最小边权
    auto dfs = [&](auto&& dfs, int u, ll fw) -> int {
        // 到达表示重构树上的叶子节点,即原图的点, 返回是不是奇点
        if(u <= n) return deg[u] % 2;

        fw = min(fw, a[u - n]);
        int cnt = 0;
        for (int v : adj[u]) {
            cnt += dfs(dfs, v, fw);
        }

        // 所有未处理的子树上的奇点的最近公共祖先是当前的 u
        ans += cnt / 2 * fw;
        cnt %= 2;

        // 传递未处理的奇点
        return cnt;
    };
    dfs(dfs, n + m, a[m]);

    cout << ans << endl;
}
posted @ 2025-11-12 21:38  风掣凧浮  阅读(29)  评论(0)    收藏  举报