最小树形图学习笔记

前言

最小树形图,简单来说就是有向图中的最小生成树问题,一般有指定的根节点。

常见解决方法是 朱-刘算法 或 tarjan 算法,一般前者已经足够优秀,而且代码很简洁。

主要思想

其实很简单。

贪心的找到除根结点之外,每个节点的最小入边,得到一个边的集合。

如果这些边中没有有向环,那么我们已经找到了最小树形图,否则需要进一步处理。

我们将所有环进行缩点,同时在答案中加上环上所有边的权值,然后用缩点后的图重复上述过程,直至无环。

但是有个问题,缩点后相当于 \(n\) 个点选择了 \(n\) 条边连接,这不满足树形图的性质。

于是有一个等效替换的思想,假设有环 \(S\),其中 \(u\to v\) 是环上的一条边,\(a\to v\) 是环外指向环上一点 \(v\) 的边。

那么显然,如果选择 \(a\to v\),就必须断开 \(u\to v\),那么等价于在重新构图时令 \(val(a\to v)-=val(u\to v)\)

那么这个问题就解决了。

当然还有无解的情况,若在寻找最短入边时有点没有入边,那么显然这个有向图不连通,即无解。

代码实现

int ans = 0, u, v;
while(true){
    for(int i = 1; i <= n; i ++) in[i] = INF;
    for(int i = 1; i <= m; i ++){
        u = e[i].u;
        v = e[i].v;
        if(u != v && in[v] > e[i].w){
            in[v] = e[i].w;
            pre[v] = u;
        }
    }
    for(int i = 1; i <= n; i ++)
        if(i != root && in[i] == INF){puts("-1"); return 0;}
    int tot = 0;
    for(int i = 1; i <= n; i ++) id[i] = vis[i] = -1;
    in[root] = 0;
    for(int i = 1; i <= n; i ++){
        ans += in[i];
        v = i;
        while(vis[v] != i && v != root && id[v] == -1){
            vis[v] = i;
            v = pre[v];
        }
        if(v != root && id[v] == -1){
            id[v] = ++ tot;
            for(int u = pre[v]; u != v; u = pre[u]) id[u] = tot;
        }
    }
    if(!tot) break;
    for(int i = 1; i <= n; i ++)
        if(id[i] == -1) id[i] = ++ tot;
    for(int i = 1; i <= m; i ++){
        u = e[i].u;
        v = e[i].v;
        e[i].u = id[u];
        e[i].v = id[v];
        if(id[u] != id[v]) e[i].w -= in[v];
    }
    n = tot;
    root = id[root];
}

时间复杂度

显然最多进行 \(n-2\) 次缩点,每次缩点需要花费 \(O(m)\) 时间。

故总时间复杂度为 \(O(nm)\)

简单例题

Stream My Contest

在有向图中给定根节点,以及能承受的最大代价,每条边都有一个代价以及一个大小。

要求选择一些边使得总代价不大于最大承受代价,且使图为树形图,在这种情况下使最小边最大,求这个最大值。

显然是套了个二分的最小树形图问题,没了。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

const int N = 100;
const int M = 10010;
const int INF = 1 << 30;
int n, m, cost, in[N], pre[N], id[N], vis[N];
struct Edge{int u, v, w, kb;} e[M], tmp[M];

int read(){
	int x = 0, f = 1; char c = getchar();
	while(c < '0' || c > '9') f = (c == '-') ? -1 : 1, c = getchar();
	while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
	return x * f;
}

int Get(int limit){
	int now = n, root = 0, u, v;
	int ans = 0;
	for(int i = 1; i <= m; i ++) tmp[i] = e[i];
	while(true){
		for(int i = 0; i < now; i ++) in[i] = INF;
		for(int i = 1; i <= m; i ++){
			if(e[i].kb < limit) continue;
			u = e[i].u;
			v = e[i].v;
			if(u != v && e[i].w < in[v]){
				in[v] = e[i].w;
				pre[v] = u;
			}
		}
		for(int i = 0; i < now; i ++){
			if(i != root && in[i] == INF){
				for(int j = 1; j <= m; j ++) e[j] = tmp[j];
				return -1;
			}
			vis[i] = id[i] = -1;
		}
		in[root] = 0;
		
		int tot = 0;
		for(int i = 0; i < now; i ++){
			ans += in[i];
			v = i;
			while(vis[v] != i && id[v] == -1 && v != root){
				vis[v] = i;
				v = pre[v];
			}
			if(id[v] == -1 && v != root){
				for(int u = pre[v]; u != v; u = pre[u]) id[u] = tot;
				id[v] = tot ++;
			}
		}
		if(!tot) break;
		for(int i = 0; i < now; i ++)
			if(id[i] == -1) id[i] = tot ++;
		for(int i = 1; i <= m; i ++){
			if(e[i].kb < limit) continue;
			u = e[i].u;
			v = e[i].v;
			e[i].u = id[u];
			e[i].v = id[v];
			if(id[u] != id[v]) e[i].w -= in[v];
		}
		now = tot;
		root = id[root];
	}
	for(int i = 1; i <= m; i ++) e[i] = tmp[i];
	return ans;
}

bool chck(int limit){
	int now = Get(limit);
	return (now != -1 && now <= cost);
}

int main(){
	int T = read();
	while(T --){
		n = read(), m = read(), cost = read();
		int l = 0, r = 0;
		for(int i = 1; i <= m; i ++){
			e[i].u = read(), e[i].v = read();
			e[i].kb = read(), e[i].w = read();
			r = max(r, e[i].kb);
		}
		while(l < r){
			int mid = (l + r + 1) >> 1;
			if(chck(mid)) l = mid;
			else r = mid - 1;
		}
		if(l == 0) puts("streaming not possible.");
		else printf("%d kbps\n", l);
	}
	return 0;
}
posted @ 2021-05-20 11:02  LPF'sBlog  阅读(68)  评论(0编辑  收藏  举报