首页 > 编程语言 >朱刘算法

朱刘算法

时间:2024-10-29 21:31:13浏览次数:4  
标签:环上 int 最小 树形图 算法 入边 节点

1 问题

我们知道带权无向图上有一个经典问题:最小生成树。那么如果换成带权有向图呢?

对于一个带权有向图,从中选出一个子图,使得该子图中无环,且存在一个点可以到达其他所有点,则这个子图就是一个树形图。而求出所有树形图中选出边权和最小的一种选法,就是最小树形图问题。

容易想到,解决最小生成树的两种方法都是基于无向成立的,因此不能套用到该问题上。我们就需要一种新的算法。

2 朱刘算法

2.1 算法流程

我们先考虑给定最小树形图的根 \(rt\) 的解法。

我们先考虑一个较简单的问题:求出一个 DAG 的最小树形图。那么显然我们只需要选出所有点的入边中最小的一条即可。

假如我们对任意图进行上述操作,会发现一个问题:我们最后选出的子图很可能带环。那么此时环上一定会有一个点,它的真正入边不再是环上对应的那条入边,而是环外指向他的一条边。而此时假设原先的入边是 \(w\),新的入边是 \(v\),那么相当于是对我们最初的答案加上了 \(v-w\)。

那么我们可以这样来做:我们对于每一个点先找出他们的最小入边并相连。接下来考虑每一个点,如果它指向的节点是一个环上的节点,设这条边边权为 \(v\),指向的点在环上的对应入边权值为 \(w\),则将这条边的权值改为 \(v-w\)。同时将所有环缩点。重复执行上述过程,直到转化为一个 DAG 上的问题即可。

那么显然经过这样的操作,我们选出来的价值一定和最小树形图是等价的。

现在只需要考虑一个问题,即如何找环。Tarjan 实际上有些复杂,我们有更简单的做法。

2.1.1 找环

令 \(pre_x\) 表示当前 \(x\) 的最小入边对应的另一个节点,\(id_x\) 表示当前节点在图上的最晚前驱(可以用 \(pre\) 跳过来的点)(类似于并查集路径压缩,也类似于 Tarjan 中的 \(low\)),\(bel_x\) 表示 \(x\) 属于哪个环(不属于环 \(bel_x=0\))。

我们遍历每一个节点 \(x\),然后沿着 \(pre\) 跳,并沿途更新 \(id_x\)。接下来我们分类讨论:

  • 如果走到了 \(rt\),那么它一定不在环上。
  • 如果走到了一个点 \(v\) 的 \(id_v=x\),则说明一定出现了环。此时还要继续讨论:
    • 如果 \(bel_v=0\),则说明这是一个新环,遍历环上节点更新 \(bel\) 即可。
    • 否则说明这个环已经被更新过了,一个节点不可能同时是两个环的节点,所以不必操作。
  • 如果走到了一个在环上的点,和上面一样的道理,我们不必操作。

这样就可以找到环并将它们缩点了。具体代码如下:

for(int i = 1; i <= n; i++) {
    int x = i;
    while(id[x] != i && bel[x] == -1 && x != rt) {//没有走到根,没有走到环上点,没有走到前驱是自己的点
        id[x] = i;//沿途更新
        x = pre[x];
    }
    if(x != rt && bel[x] == -1) {//找到新环
        bel[x] = ++cnt;//更新 bel
        for(int j = pre[x]; j != x; j = pre[j]) bel[j] = cnt;
    }
}

有了这个关键的技术,剩下的部分就不难完成了。

上面我们探讨的都是给定根的情况,如果不指定根呢?很简单,我们新建超级源点,向所有点连一条 \(\infty\) 的边。这样如果有解,这样的边一定只会选一条,减掉即可;而如果无解,则这样的边会选多条,判断即可。

2.2 完整代码

模板题:【模板】最小树形图。代码如下:

#include <bits/stdc++.h>
#define int long long

using namespace std;

const int Maxn = 2e5 + 5;
const int Inf = 1e13;

int n, m, rt;
struct node {
	int u, v, w;
}e[Maxn];

int cnt, bel[Maxn], pre[Maxn], mn[Maxn], id[Maxn];

int Chu_Liu() {
	int ans = 0;
	while(1) {
		for(int i = 1; i <= n; i++) mn[i] = Inf, pre[i] = 0;
		for(int i = 1; i <= m; i++) {//找最小入边
			if(e[i].u != e[i].v && e[i].w < mn[e[i].v]) {
				mn[e[i].v] = e[i].w;
				pre[e[i].v] = e[i].u;
			}
		}
		for(int i = 1; i <= n; i++) if(i != rt && mn[i] == Inf) return -1;
        //如果有点没有最小入边,则一定不可能联通
		for(int i = 1; i <= n; i++) id[i] = bel[i] = -1;
		cnt = 0;
		mn[rt] = 0;
		for(int i = 1; i <= n; i++) {//找环
			int x = i;
			ans += mn[i];//直接求和
			while(id[x] != i && bel[x] == -1 && x != rt) {
				id[x] = i;
				x = pre[x];
			}
			if(x != rt && bel[x] == -1) {
				bel[x] = ++cnt;
				for(int j = pre[x]; j != x; j = pre[j]) bel[j] = cnt;
			}
		}
		if(cnt == 0) break;
		for(int i = 1; i <= n; i++) if(bel[i] == -1) bel[i] = ++cnt;
		for(int i = 1; i <= m; i++) {//重建边
			int tmp = e[i].v;
			e[i].u = bel[e[i].u];
			e[i].v = bel[e[i].v];
			if(e[i].u != e[i].v) e[i].w -= mn[tmp];//修改边权
		} 
		n = cnt;
		rt = bel[rt];
	}
	return ans;
}

signed main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	cin >> n >> m >> rt;
	for(int i = 1; i <= m; i++) {
		cin >> e[i].u >> e[i].v >> e[i].w;
	}
	cout << Chu_Liu();
	return 0;
}

标签:环上,int,最小,树形图,算法,入边,节点
From: https://www.cnblogs.com/UKE-Automation/p/18514547

相关文章

  • Offer68题 Day3 两个基础算法
    1.DFS深度优先算法/* -深度优先算法 DFS从起始节点出发,沿着一条路径尽可能深入地访问每个节点,直到无法继续时再回退,寻找未访问的节点。 -使用递归实现。*/#include<iostream>#include<vector>usingnamespacestd;voidDFS(intnode,vector<vector<int>>&gra......
  • 代码随想录算法训练营day30| 452. 用最少数量的箭引爆气球 435. 无重叠区间 763.
    学习资料:https://programmercarl.com/0452.用最少数量的箭引爆气球.html重叠区域问题最远位置问题452.用最少数量的箭引爆气球(重叠区域;按左边界排序;i区间的左边界与i-1区间的右边界比较来确定是否重叠;更新i的右边界,取i与i-1区域右边界的最小值)点击查看代码classSolution(ob......
  • 算法刷题记录(day5)
    LC160.相交链表题目描述:给你两个单链表的头节点headA和headB,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回null。classSolution{public:ListNode*getIntersectionNode(ListNode*headA,ListNode*headB){if(head......
  • 字符串匹配-KMP算法实现代码
    字符串的基本操作同上一篇BF算法一致一.为模式串创建临时数组//KMP算法//1)为模式串创建临时数组voidcomputeLPSArray(char*pat,intM,int*lps){ //指向首元素 intj=0; //指向首元素的下一个元素 inti=1; //临时数组的首元素总为0 lps[0]=0; //结束条......
  • 【算法】分治算法-让问题消失
    博主推荐!!!:"近期我偶然邂逅了一个极为出色的人工智能学习平台,它不仅内容深入浅出,讲解方式还风趣幽默,让人学习起来既轻松又高效。如此宝藏资源,我迫不及待想要与各位共享,让我们一起进入这个精彩纷呈的学习网站吧!"即刻点击https://www.captainbed.cn/cyy算法系列:今天,我们要聊的......
  • 【算法学习】基环树
    基环树基环树就是类似于在树上加了一条边形成了环,去点环上的一条边后就会变成数,如下图。这是一个\(n\)个点\(n\)条边的连通图,如果不保证联通,它就会成为基环树森林。外向树:每个点都只有一条入边,因为向内上。内向树:每个点都只有一条出边,因为向外少。怎么用呢?因为有环的性......
  • 算法:查找
    算法1.顺序查找和折半查找1.1顺序查找1.2折半查找1.3索引顺序查找2.树表查找2.1查找2.2插入3.哈希表及哈希查找3.1哈希造表3.2处理冲突开放定址法链地址法3.3哈希查找查找是非数值数据处理中一种基本运算,查找运算的效率与查找表所采用的数据结构和查找......
  • 解码小红书CES算法,让你的笔记阅读量提升100%
    随着社交媒体成为日常生活的一部分,内容创作者们都在积极寻找提高作品可见性的方法。作为社交分享领域的佼佼者,小红书凭借其独特的CES算法机制,在众多平台中脱颖而出。本文将深入探讨小红书的CES算法工作原理,并提供实用技巧,帮助你显著提升笔记的阅读量。一、小红书CES算法解......
  • (算法)最⻓公共⼦序列————<动态规划>
    1.题⽬链接:1143.最⻓公共⼦序列2.题⽬描述:3.解法(动态规划):算法思路:1.状态表⽰:对于两个数组的动态规划,我们的定义状态表⽰的经验就是:        i.选取第⼀个数组[0,i]区间以及第⼆个数组[0,j]区间作为研究对象;        ii.结合题⽬要求,定义状态......
  • 点云学习笔记4——点云滤波降采样后进行4PCS粗配准【四点一致集配准算法(4-Point Congr
    #include<iostream>#include<pcl/point_cloud.h>#include<pcl/point_types.h>#include<pcl/filters/voxel_grid.h>#include<pcl/common/common_headers.h>#include<pcl/io/pcd_io.h>#include<pcl/visualization/cloud_vi......