欢迎来到李一诺的博客|

Weekoder

园龄:1年1个月粉丝:2关注:0

负环的习题和应用

update 2024/6/9:  文中提到了速度较快的 DFS-SPFA,虽然速度较好,但是容易被卡,例如模板!

大家好,我是Weekoder!

今天要讲的是负环的一些习题与负环在题目中的应用。

一共有 3 个例题,分别为:

P2136P2868P3199。(点击有 Link

P2136 拉近距离

题面

在小明和小红的生活中,有 N 个关键的节点。有 M 个事件,记为一个三元组 (Si,Ti,Wi),表示从节点 Si 有一个事件可以转移到 Ti,事件的效果就是使他们之间的距离减少 Wi

这些节点构成了一个网络,其中节点 1N 是特殊的,节点 1 代表小明,节点 N 代表小红,其他代表进展的阶段。所有事件可以自由选择是否进行,但每次只能进行当前节点邻接的。请你帮他们写一个程序,计算出他们之间可能的最短距离。如果这个距离可以无限缩小,输出Forever love

分析

可以把题意理解为一个人在一条路径上不断往另一个人的方向走。

注意到题目中说距离会减少 Wi,那么边权即为负。正常来说,跑一遍 SPFA 就可以了,答案是 disn。但是注意到不仅是小明可以去找小红,小红也可以去找小明,于是做两遍 SPFA,分别为 spfa(1)spfa(n),最后取 minForever love 就是判断有没有负环,除此之外和 SPFA 判负环没有区别。由于没有负环要输出最短路,所以考虑使用 BFS-SPFA。

Code:

#include <bits/stdc++.h>
using namespace std;

const int N = 1e3 + 5;

struct Edge {
	int to, w;
};

int n, m, dis[N], dp[N];
bool vis[N];

vector<Edge> nbr[N];

bool spfa(int s) {
	queue<int> q;
	memset(vis, 0, sizeof vis);
	memset(dis, 0x3f, sizeof dis);
	memset(dp, 0, sizeof dp);
	dis[s] = 0;
	vis[s] = 1;
	q.push(s);
	while (!q.empty()) {
		int cur = q.front(); q.pop();
		vis[cur] = 0;
		for (auto nxt : nbr[cur]) {
			int to = nxt.to, w = nxt.w;
			if (dis[to] > dis[cur] + w) {
				dis[to] = dis[cur] + w;
				dp[to] = dp[cur] + 1;
				if (dp[to] >= n) return 1;
				if (!vis[to]) {
					vis[to] = 1;
					q.push(to);
				}
			}
		}
	}
	return 0;
}

int main() {
	cin >> n >> m;
	for (int i = 1, u, v, w; i <= m; i++) {
		cin >> u >> v >> w;
		nbr[u].emplace_back((Edge){v, -w});
	}
	if (spfa(1)) {
	    cout << "Forever love";
	    return 0;
	}
	int tmp = dis[n];
	if (spfa(n)) {
	    cout << "Forever love";
	    return 0;
	}
	cout << min(dis[1], tmp);
	return 0;
}

P2868 Sightseeing Cows G

题面

给你一张 nm 边的有向图,第 i 个点点权为 Fi,第 i 条边边权为 Ti

找一个环,设环上的点组成的集合为 S,环的边组成的集合为 E,最大化 uSFueETe

分析

考虑二分答案。

设二分答案 mid,因为要找最大值,所以我们设还有一个更大值。这个值就是 uSFueETe,所以假设我们有:uSFueETe>mid。如果成立,就猜大些,否则猜小些。但是这个不好判断,所以我们来推一下式子(在一个环中,点数 = 边数):

uSFueETe>mid=uSFu>mideETe=mideETeuSFu<0=(midTeFu)<0

推到这里,我们发现了一个有趣的结论:若想继续猜大些,则需要满足对于环上的边权之和,有 midTeFu<0。我们把 midTeFu 看作边权,那这不就变成了一个找负环的问题吗?没错,在简单的计算后,我们得到了一个结论:找负环。

在这里,就仅仅需要判断是否有负环并二分答案,推荐使用 DFS-SPFA,用时仅有 46ms。对于 BFS-SPFA,也能过,不过时间为 83ms

Code:

DFS-SPFA

#include <bits/stdc++.h>
using namespace std;

const int N = 1e3 + 5;
const double eps = 1e-6;

struct Edge {
    int to;
    double w;
};

int n, m;
double dis[N], f[N];
bool vis[N];

vector<Edge> nbr[N];

bool spfa(int cur, double X) {
    vis[cur] = 1;
    for (auto nxt : nbr[cur]) {
        int to = nxt.to;
        double w = nxt.w * X - f[cur];
        if (dis[to] > dis[cur] + w) {
            dis[to] = dis[cur] + w;
            if (vis[to] || spfa(to, X)) return 1;
        }
    }
    vis[cur] = 0;
    return 0;
}

bool check(double X) {
    memset(vis, 0, sizeof vis);
    memset(dis, 0, sizeof dis);
    for (int i = 1; i <= n; i++)
        if (spfa(i, X))
            return 1;
    return 0;
}

int main() {
    cin >> n >> m;
    for (int i = 1; i <= n; i++) cin >> f[i];
    double l = 1005, r = -1;
    for (int i = 1, u, v; i <= m; i++) {
        double w;
        cin >> u >> v >> w;
        nbr[u].emplace_back((Edge){v, w});
        l = min(l, w);
        r = max(r, w);
    }
    r++, l--;
    while (r - l > eps) {
        double mid = (l + r) / 2;
        if (check(mid)) l = mid;
        else r = mid;
    }
    printf("%.2lf", l);
    return 0;
}

BFS-SPFA

#include <bits/stdc++.h>
using namespace std;

const int N = 1e3 + 5;
const double eps = 1e-6;

struct Edge {
	int to;
	double w;
};

int T, n, m, dp[N];
double dis[N], f[N];
bool vis[N];

vector<Edge> nbr[N];

bool spfa(double X) {
	queue<int> q;
	memset(vis, 0, sizeof vis);
	memset(dis, 0, sizeof dis);
	memset(dp, 0, sizeof dp);
	for (int i = 1; i <= n; i++)
		vis[i] = 1, q.push(i);
	while (!q.empty()) {
		int cur = q.front(); q.pop();
		vis[cur] = 0;
		for (auto nxt : nbr[cur]) {
			int to = nxt.to;
			double w = X * nxt.w - f[cur];
			if (dis[to] > dis[cur] + w) {
				dis[to] = dis[cur] + w;
				dp[to] = dp[cur] + 1;
				if (dp[to] >= n) return 1;
				if (!vis[to]) {
					vis[to] = 1;
					q.push(to);
				}
			}
		}
	}
	return 0;
}

int main() {
	cin >> n >> m;
	for (int i = 1; i <= n; i++) cin >> f[i];
	double l = 1005, r = -1;
	for (int i = 1, u, v; i <= m; i++) {
		double w;
		cin >> u >> v >> w;
		nbr[u].emplace_back((Edge){v, w});
		l = min(l, w);
		r = max(r, w);
	}
	double tmp = r + 1;
	r++, l--;
	while (r - l > eps) {
		double mid = (l + r) / 2;
		if (spfa(mid)) l = mid;
		else r = mid;
	}
	printf("%.2lf", l);
	return 0;
}

P3199 最小圈

题面

考虑带权有向图 G=(V,E) 以及 w:ER,每条边 e=(i,j)iji,jV)的权值定义为 wi,j。设 n=|V|

c=(c1,c2,,ck)ciV)是 G 中的一个圈当且仅当 (ci,ci+1)1i<k)和 (ck,c1) 都在 E 中。称 k 为圈 c 的长度,同时记 ck+1=c1,并定义圈 c=(c1,c2,,ck) 的平均值为

μ(c)=1ki=1kwci,ci+1

c 上所有边的权值的平均值。设 μ(G)=mincμ(c)G 中所有圈 c 的平均值的最小值。

给定图 G=(V,E) 以及 w:ER,求出 G 中所有圈 c 的平均值的最小值 μ(G)

题面概括

给定一个图,找一个环,最小化

1ki=1kwci,ci+1

分析

考虑二分答案。

设二分答案 mid,这次要找最小值,于是设 1ki=1kwci,ci+1<mid。继续推公式:

1ki=1kwci,ci+1<mid=midk>i=1kwci,ci+1=i=1k(midwci,ci+1)>0=i=1k(wci,ci+1mid)<0

于是我们又得到了:将边权看作原边权 mid,判负环。

这个题需要用 DFS-SPFA,否则会 TLE50pts

Code:

#include <bits/stdc++.h>
using namespace std;

const int N = 3e3 + 5;
const double eps = 1e-9;

struct Edge {
    int to;
    double w;
};

int n, m;
double dis[N];
bool vis[N];

vector<Edge> nbr[N];

bool spfa(int cur, double X) {
    vis[cur] = 1;
    for (auto nxt : nbr[cur]) {
        int to = nxt.to;
        double w = nxt.w - X;
        if (dis[to] > dis[cur] + w) {
            dis[to] = dis[cur] + w;
            if (vis[to] || spfa(to, X)) return 1;
        }
    }
    vis[cur] = 0;
    return 0;
}

bool check(double X) {
    memset(dis, 0, sizeof dis);
    memset(vis, 0, sizeof vis);
    for (int i = 1; i <= n; i++)
        if (spfa(i, X)) return 1;
    return 0;
}
int main() {
    cin >> n >> m;
    double l = 1e8, r = -1e8;
    for (int i = 1, u, v; i <= m; i++) {
        double w;
        cin >> u >> v >> w;
        nbr[u].emplace_back((Edge){v, w});
        l = min(l, w);
        r = max(r, w);
    }
    r++, l--;
    while (r - l > eps) {
        double mid = (l + r) / 2;
        if (check(mid)) r = mid;
        else l = mid;
    }
    printf("%.8lf", r);
    return 0;
}

小结

以上就是关于负环的三个习题的内容。负环是一个判定型的算法,适合搭配二分答案等算法,发挥更大的用处。今天的习题都有一些难度,需要巩固练习。

再见!

本文作者:Weekoder

本文链接:https://www.cnblogs.com/Weekoder/p/18240228

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   Weekoder  阅读(13)  评论(0编辑  收藏  举报
评论
收藏
关注
推荐
深色
回顶
收起
点击右上角即可分享
微信分享提示