P3199 最小圈
2023-11-02 22:53:24 # OI # Problem

题面

题目描述

考虑带权的有向图G=(V,E)G=(V,E)以及w:ERw:E\rightarrow R,每条边e=(i,j)(ij,iV,jV)e=(i,j)(i\neq j,i\in V,j\in V)的权值定义为wi,jw_{i,j},令n=Vn=|V|c=(c1,c2,,ck)(ciV)c=(c_1,c_2,\cdots,c_k)(c_i\in V)GG中的一个圈当且仅当(ci,ci+1)(1i<k)(c_i,c_{i+1})(1\le i<k)(ck,c1)(c_k,c_1)都在EE中,这时称kk为圈cc的长度同时令ck+1=c1c_{k+1}=c_1,并定义圈c=(c1,c2,,ck)c=(c_1,c_2,\cdots,c_k)的平均值为μ(c)=i=1kwci,ci+1/k\mu(c)=\sum\limits_{i=1}^{k} w_{c_i,c_{i+1}}/k,即cc上所有边的权值的平均值。令μ(c)=Min(μ(c))\mu'(c)=Min(\mu(c))GG中所有圈cc的平均值的最小值。现在的目标是:在给定了一个图G=(V,E)G=(V,E)以及w:ERw:E\rightarrow R之后,请求出GG中所有圈cc的平均值的最小值μ(c)=Min(μ(c))\mu'(c)=Min(\mu(c))

输入格式

第一行2个正整数,分别为nnmm,并用一个空格隔开,只用n=V,m=En=|V|,m=|E|分别表示图中有nn个点mm条边。
接下来m行,每行3个数i,j,wi,ji,j,w_{i,j},表示有一条边(i,j)(i,j)且该边的权值为wi,jw_{i,j}。输入数据保证图G=(V,E)G=(V,E)连通,存在圈且有一个点能到达其他所有点。

输出格式

请输出一个实数μ(c)=Min(μ(c))\mu'(c)=Min(\mu(c)),要求输出到小数点后8位。

样例 #1

样例输入 #1

4 5
1 2 5
2 3 5
3 1 5
2 4 3
4 1 3

样例输出 #1

3.66666667

样例 #2

样例输入 #2

2 2
1 2 -2.9
2 1 -3.1

样例输出 #2

-3.00000000

提示

对于100%的数据,n3000,m10000,wi,j107n\le 3000,m\le 10000,|w_{i,j}| \le 10^7

思路

通过二分答案将问题转化为存在性问题。

如果 μ(c)<mid\mu(c)<mid,那么 r=midr=mid,否则 l=midl=mid

那么如何判断 μ(c)<mid\mu(c)<mid 呢?

展开看看:

μ(c)=i=1kwci,ci+1k<mid\mu(c) = \frac{\sum_{i=1}^{k} w_{c_i,c_{i+1}}}{k}<mid

i=1kwci,ci+1k×mid<0\sum_{i=1}^{k} w_{c_i,c_{i+1}}-k\times{mid}<0

i=1kwci,ci+1mid<0\sum_{i=1}^{k} w_{c_i,c_{i+1}}-mid<0

那么 μ(c)<mid\mu(c)<mid 成立,即边权都减去 midmid 的情况下有负环存在。

这里判断其负环使用 DFS 式 的 SPFA。

跑一遍最短路,按照边权更新的逻辑,假如成功松弛才继续递归,这个时候假如出现了环,那么一定是负环,因为这个环上的每一条边从起点走到终点都会使 distdist 值变短,而 distdist 初始值是 00,所以走过一遍就会使这个环的总 distdist 变成负数,也就是出现了负环。

但是这个环是一定经过起始点的一个环,所以我们要把每个点 DFS 一遍,直到找到负环。

代码

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 3010, M = 10010, INF = 1e7;
const double EPS = 1e-10;

int n, m, cnt;
bool vis[N];
double dist[N];
int fir[N];
struct Edge
{
	int to, nxt;
	double val;
}e[M << 1];

void add(int u, int v, double w)
{
	e[++cnt].to = v;
	e[cnt].nxt = fir[u];
	e[cnt].val = w;
	fir[u] = cnt;
}
bool check(int u, double x)
{
	vis[u] = true;
	for (int i = fir[u]; i; i = e[i].nxt)
	{
		int v = e[i].to;
		if(dist[v] > dist[u] + e[i].val - x)
		{
			dist[v] = dist[u] + e[i].val - x;
			if(vis[v] || check(v, x)) return true; // ��� 
		}
	}
	vis[u] = false;
	return false;
}
bool Check(double x) 
{
	memset(dist, 0.0, sizeof(dist));
	memset(vis, 0, sizeof(vis));
	for (int i = 1; i <= n; i++) if(check(i, x)) return true;
	return false;
}

int main()
{
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= m; i++)
	{
		int u, v;
		double w;
		scanf("%d%d%lf", &u, &v, &w);
		add(u, v, w);
	}
	double l = -INF, r = INF;
	while(r - l > EPS)
	{
		double mid = (l + r) / 2.0;
		if(Check(mid)) r = mid;
		else l = mid;
	}
	printf("%.8lf\n", l);
	return 0;
}