最小树形图

感悟于

  • 最小树形图,是对有向图求解最小生成树的过程,同样地边权和最小。
  • 先说朱流算法
  • 首先我们给定一个根 \(rt\),观察树形图的树结构

image-20250319161348784

  • 每个节点除了根,仅有一个入边,那么我们对于所有的点都贪心地选出一条入边最小的边,希望最终的图中能有它。
  • 可是往往结果并不乐观,我们可能出环。那怎么办呢?环内迟早要和环外相连,所以我们可以把环看成一个整体。

image-20250319162316731

  • 如这个图所见,若我们考虑这个环,看作整体选择最小的入边为蓝色,需要删除那条边,绿色同理。那我们不妨把所有环上所有边累加近答案,然后把环所点,对于环上的每一个点,的所有入边,的边权都要减去环上这个点入边的边权,因为若选了这个点联通整个环,回导致这条边被删,于是为了环内的点相对大小关系以及方便答案的角度考虑。我们减去这个边权。
  • 好!其实朱流算法到这里就结束了
  • 但我们还有 可并堆优化 不知道是不是 tarjan 提出的,但好像和 wiki 上写的不太一样。
  • 注意到,复杂度瓶颈在于对所有的边权统一减一个值,还有合并一些边。
  • 我们显然可以使用可并堆来作的。
  • 然后讲过程,由于根没有入边,所以每次找一个不是根的点,然后找这个点的最小入边,直到,找到找到一个已经处理过的点,若这个已经处理过的点不在本次搜索的点中,我们相当于接在了一个树的后面,直接结束即可。
  • 若是在本次搜索中相当于找到了一个环,于是缩点,合并边,接着没完,我们我们继续沿着合并的点走,直到走到非本轮搜索的点,连到树里。
  • 注意我们合并完边之后可能出现自己连向自己的边用并查集判掉就好了。
  • 注意啊 左偏树 pop 的时候需要,记得下传标记啊
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
int n, m, rt;
const int N = 220, M = 15000;
const int inf = 0x3f3f3f3f;
struct edge { int u, v, w; }e[M];
struct node
{
    node* ls, * rs;
    int dis, val, id, tag;
    node(int _id) { id = _id; val = e[id].w; tag = 0; dis = 1; ls = rs = nullptr; }
    void down()
    {
        if (!tag)return;
        if (ls)ls->tag += tag, ls->val += tag;
        if (rs)rs->tag += tag, rs->val += tag;
        tag = 0;
    }
};
node* merge(node* a, node* b)
{
    if (!a)return b; if (!b)return a;
    if (a->val > b->val)swap(a, b);
    a->down(); a->rs = merge(a->rs, b);
    if (!a->ls)return swap(a->ls, a->rs), a->dis = 1, a;
    if (a->ls->dis < a->rs->dis)swap(a->ls, a->rs);
    a->dis = a->rs->dis + 1; return a;
}
void pop(node*& x) { x->down(); x = merge(x->ls, x->rs); } // down !!!
void add(node* x, int v) { if (x) { x->val += v;x->tag += v; } }
void push(node*& x, int id) { x = merge(x, new node(id)); }
node* Q[N];
int vis[N], f[N], cnt;
int getf(int x) { return f[x] < 0 ? x : f[x] = getf(f[x]); }
void clear(int u) { while (getf(e[Q[u]->id].u) == u)pop(Q[u]); }
int next(int u) { return getf(e[Q[u]->id].u); }
int sum = 0;
void solve(int u)
{
    int x = u;
    while (vis[x] == 0)
    {
        while (vis[x] == 0) vis[x] = u, sum += Q[x]->val, x = next(x);
        if (vis[x] != u)return; cnt++; vector<int> h;
        do { h.push_back(x);x = next(x); } while (x != h.front());
        for (int x : h)add(Q[x], -Q[x]->val), Q[cnt] = merge(Q[cnt], Q[x]), f[x] = cnt;
        clear(x = cnt);
    }
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);cout.tie(nullptr);
    cin >> n >> m >> rt; cnt = n;
    for (int i = 1;i <= m;i++)
    {
        cin >> e[i].u >> e[i].v >> e[i].w;
        push(Q[e[i].v], i);
    }
    for (int i = 1;i <= n;i++)
    {
        e[m + i] = { i,i % n + 1,inf };
        push(Q[i % n + 1], m + i);
    }
    for (int i = 1;i <= (n * 2);i++)f[i] = -1;
    for (int i = 1;i <= n;i++)clear(i);
    vis[rt] = rt;
    for (int i = 1;i <= cnt;i++) if (!vis[i]) solve(i);
    cout << sum << '\n';
    return 0;
}
posted @ 2025-04-23 08:01  LUHCUH  阅读(22)  评论(0)    收藏  举报