首页 > 其他分享 >最小树形图学习笔记

最小树形图学习笔记

时间:2023-12-16 17:33:50浏览次数:32  
标签:return int 最小 笔记 树形图 operator Modint friend

最小树形图学习笔记

退役前想学但没时间学的 useless algorithm,退役后找时间都学掉。这是其中之一。

有向图上的最小生成树称为最小树形图(Directed Minimum Spanning Tree)。

本文默认树形图为外向树,即除根以外的所有点的入度为 \(1\),根的入度为 \(0\)。

最小树形图问题即求一个有向图的以 \(rt\) 节点为根的最小树形图。

1965 年,朱永津和刘振宏提出了最小树形图的朱刘算法。

朱刘算法与无向图的 Boruvka 最小生成树算法有一些异曲同工之处,其算法流程如下:

  1. 对于每个非根节点,找到其最小入边,记录边权 \(val\) 以及边的起点 \(vtx\)。
  2. 如果存在一个非根节点不存在入边,返回无解,算法结束。
  3. 将每个非根节点的入边边权累加进答案。
  4. 如果无环,返回答案,算法结束。
  5. 将每个环缩成一个点,删除所有环上的边。对于每条不在环上的边 \((u,v,w)\),将其边权减去 \(val(v)\)。回到步骤一。

算法对无解的判定和有向无环图的情况是显然正确的。可以证明,当找到一个环时,一定存在一个最小树形图,包含环上除了一条边以外的所有边。算法步骤五中对边权的修改相当于一个反悔的过程,保证了算法的正确性。

显然,算法的每一轮会缩掉至少一个环,使得点数至少减少一。算法每一轮的时间复杂度为 \(O(m)\),因此总复杂度为 \(O(nm)\)。

模板题:最小树形图Teen Girl Squad

代码
// Problem: P4716 【模板】最小树形图
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P4716
// Memory Limit: 250 MB
// Time Limit: 1000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

//By: OIer rui_er
#include <bits/stdc++.h>
#define rep(x, y, z) for(int x = (y); x <= (z); ++x)
#define per(x, y, z) for(int x = (y); x >= (z); --x)
#define debug(format...) fprintf(stderr, format)
#define fileIO(s) do {freopen(s".in", "r", stdin); freopen(s".out", "w", stdout);} while(false)
#define endl '\n'
using namespace std;
typedef long long ll;

mt19937 rnd(std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::system_clock::now().time_since_epoch()).count());
int randint(int L, int R) {
    uniform_int_distribution<int> dist(L, R);
    return dist(rnd);
}

template<typename T> void chkmin(T& x, T y) {if(x > y) x = y;}
template<typename T> void chkmax(T& x, T y) {if(x < y) x = y;}

template<int mod>
inline unsigned int down(unsigned int x) {
	return x >= mod ? x - mod : x;
}

template<int mod>
struct Modint {
	unsigned int x;
	Modint() = default;
	Modint(unsigned int x) : x(x) {}
	friend istream& operator>>(istream& in, Modint& a) {return in >> a.x;}
	friend ostream& operator<<(ostream& out, Modint a) {return out << a.x;}
	friend Modint operator+(Modint a, Modint b) {return down<mod>(a.x + b.x);}
	friend Modint operator-(Modint a, Modint b) {return down<mod>(a.x - b.x + mod);}
	friend Modint operator*(Modint a, Modint b) {return 1ULL * a.x * b.x % mod;}
	friend Modint operator/(Modint a, Modint b) {return a * ~b;}
	friend Modint operator^(Modint a, int b) {Modint ans = 1; for(; b; b >>= 1, a *= a) if(b & 1) ans *= a; return ans;}
	friend Modint operator~(Modint a) {return a ^ (mod - 2);}
	friend Modint operator-(Modint a) {return down<mod>(mod - a.x);}
	friend Modint& operator+=(Modint& a, Modint b) {return a = a + b;}
	friend Modint& operator-=(Modint& a, Modint b) {return a = a - b;}
	friend Modint& operator*=(Modint& a, Modint b) {return a = a * b;}
	friend Modint& operator/=(Modint& a, Modint b) {return a = a / b;}
	friend Modint& operator^=(Modint& a, int b) {return a = a ^ b;}
	friend Modint& operator++(Modint& a) {return a += 1;}
	friend Modint operator++(Modint& a, int) {Modint x = a; a += 1; return x;}
	friend Modint& operator--(Modint& a) {return a -= 1;}
	friend Modint operator--(Modint& a, int) {Modint x = a; a -= 1; return x;}
	friend bool operator==(Modint a, Modint b) {return a.x == b.x;}
	friend bool operator!=(Modint a, Modint b) {return !(a == b);}
};

const int N = 105, M = 1e4 + 5, inf = 0x1f1f1f1f;

int n, m, rt, val[N], vtx[N], id[N], vis[N];

struct Edge {
    int u, v, w;
    Edge(int u = 0, int v = 0, int w = 0) : u(u), v(v), w(w) {}
    friend istream& operator>>(istream& in, Edge& e) {
        return in >> e.u >> e.v >> e.w;
    }
    friend ostream& operator<<(ostream& out, Edge e) {
        return out << e.u << " " << e.v << " " << e.w;
    }
}e[M];

int ZhuLiu(int rt) {
    int ans = 0;
    while(true) {
        rep(i, 1, n) {
            val[i] = inf;
            vtx[i] = id[i] = vis[i] = 0;
        }
        rep(i, 1, m) {
            if(e[i].u != e[i].v && e[i].w < val[e[i].v]) {
                val[e[i].v] = e[i].w;
                vtx[e[i].v] = e[i].u;
            }
        }
        val[rt] = 0;
        rep(i, 1, n) {
            if(val[i] == inf) return -1;
            ans += val[i];
        }
        int cnt = 0;
        rep(i, 1, n) {
            if(i != rt) {
                int u = i;
                while(u != rt && vis[u] != i && !id[u]) {
                    vis[u] = i;
                    u = vtx[u];
                }
                if(u != rt && !id[u]) {
                    id[u] = ++cnt;
                    for(int v = vtx[u]; v != u; v = vtx[v]) id[v] = cnt;
                }
            }
        }
        if(!cnt) return ans;
        rep(i, 1, n) if(!id[i]) id[i] = ++cnt;
        rep(i, 1, m) {
            e[i].w -= val[e[i].v];
            e[i].u = id[e[i].u];
            e[i].v = id[e[i].v];
        }
        n = cnt;
        rt = id[rt];
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    cin >> n >> m >> rt;
    rep(i, 1, m) cin >> e[i];
    cout << ZhuLiu(rt) << endl;
    return 0;
}

最小树形图有更优秀的 Tarjan 算法,可以做到 \(O(m+n\log n)\) 的复杂度,但我还没学。

如果根没有指定,可以建超级源向每个点连接边权为 \(+\infty\) 的边,之后求以超级源为根的最小树形图。

标签:return,int,最小,笔记,树形图,operator,Modint,friend
From: https://www.cnblogs.com/ruierqwq/p/directed-mst.html

相关文章

  • 拟阵学习笔记(各处抄的,未完)
    昨天CMDround要用♿不会就来学了......
  • 《程序员修炼之道:从小工到专家》阅读笔记(7)
    第36节主要讨论了在项目开始之前的一些准备步骤和流程。作者强调了需求识别的重要性,并提出需求是与用户共同完成的“发现”过程,而不仅仅是收集他们的意见。需求在某种程度上应该保持抽象,因为需求不等同于架构或设计。作者还提到了一个词汇表的维护,这是为了消除歧义,并确保大家对需......
  • ml.net例子笔记2-概念和Widnows AI Studio
    一机器学习和ml.net1Python机器学习库在Python中,工具和库的生态系统可以分为五个主要领域:数据处理数据可视化数值计算模型训练神经网络这可能不全,因为此外还有其他许多的库,它们负责其他任务,并专注于机器学习的一些特定领域,比如自然语言处理和图像识别。使用Python......
  • 12月15日《软件需求十步走》阅读笔记二
    树立正确的软件需求的概念,信息化普及的当代,人们大都着重于软件开发,很轻易的就会忽略开发的最初需求分析,这样导致最后的开发受阻或终止等问题时,就会在时间、人力、物力方面造成大量浪费,那对于开发过程十分重要的需求分析过程,我们有什么好方法可以应用呢?我们一直在寻找真正的“完整......
  • 【笔记】2023.12.16 动态规划
    笔记2023.12.16:动态规划今天题目很多,可能有些题不口胡了。LOJ6089小Y的背包计数问题前\(\sqrtn\)个物品直接做单调队列优化是\(O(n\sqrtn)\)。大于\(\sqrtn\)的是完全背包。考虑到完全背包\(v\)的OGF为\(\dfrac{1}{1-x^{v}}\)。这不行。你考虑到对于一个物......
  • 阅读笔记《探索需求》2
    第六章讲的是自由问题,第一点为什么,自由提问让你在设计过程中找到那些有关全局问题,这样你就能够进入正确的方向,而远胜于孤立无援。由于他们对所有涉及项目都是使用的,所以他们可以提前准备好并且在一个接一个的项目中使用。第二点什么时候,自由提问应该在需求过程的早期提供,它们必须......
  • 读程序员的README笔记12_On-Call
    1. 行为准则2. On-Call工程师2.1. On-Call工程师是应对计划外工作的第一道防线,无论是生产环境问题还是临时支持请求2.2. 将深度工作与运维工作分开,可以让团队中的大多数人专注于开发任务2.3. On-Call工程师只需专注于不可预知的运维难题和支持任务3. On-Call的工作方......
  • 12.15每日总结(阅读笔记8)
    《人月神话》这本书是软件工程类的一本经典著作。阅读这本书的第一感受就是感觉这本书不像是一种和学习相关的书,更像是用很多形象的比喻,阐述项目管理当中的一些问题,让读者能够很轻松,明白的去阅读。一般在大学学习计算机行业的时候,都会学习一门叫做软件工程的课程,老师也会跟我们讲......
  • 算法学习笔记四一插入排序
    目录什么是插入排序算法原理示例代码什么是插入排序插入排序可理解为扑克牌摸牌的过程,手中的牌为有序序列,然后随机摸一张牌,根据牌的大小插入到有序序列对应的位置。算法时间复杂度为O(n^2)算法原理默认列表第一个元素为基准,从第二个元素和第一个元素进行比较,并放入到相应位置......
  • 算法学习笔记三一选择排序
    目录什么是选择排序算法原理示例代码什么是选择排序选择排序的主要思想是(升序为例):第一次从待排序的数据元素中选出最小的一个元素,和数组的起始位置元素进行交换,然后再从剩余的未排序元素中寻找到最小元素,然后和未排序的序列的第一个元素进行交换。每次在未排序序列中选择一个最......