361. 观光奶牛(小数二分,spfa判断正环,01分数规划)

361. 观光奶牛 - AcWing题库

给定一张 L 个点、P 条边的有向图,每个点都有一个权值 f[i],每条边都有一个权值 t[i]。

求图中的一个环,使“环上各点的权值之和”除以“环上各边的权值之和”最大。

输出这个最大值。

注意:数据保证至少存在一个环。

输入格式

第一行包含两个整数 L 和 P。

接下来 L 行每行一个整数,表示 f[i]。

再接下来 P 行,每行三个整数 a,b,t[i],表示点 a 和 b 之间存在一条边,边的权值为 t[i]。

输出格式

输出一个数表示结果,保留两位小数。

数据范围

2≤L≤1000
2≤P≤5000,
1≤f[i],t[i]≤1000

输入样例:
5 7
30
10
10
5
10
1 2 3
2 3 2
3 4 5
3 5 2
4 5 5
5 1 3
5 2 2
输出样例:
6.00

解析: 

所有在图论问题中形如:一堆的和除以一堆的和,求这个结果的最大值的问题,我们称之为01分数规划”

一般此类为题我们同有一种通用的做法———二分。

为了方便写二分算法,我们将式子转换成不含出发的式子:sum(f[i])/sum(t[i])>mid==>sum(f[i])-mid*sum(t[i])>0;

我们将点上的权值放到边上那么上述公式就变成了:sum(f[i]-mid*t[i])>0;

每条边的边权就变成了:f[i]-mid*t[i]

问题至此就等价与图中是存在正环,使得sum(f[i]-mid*t[i])>0成立;

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<ctime>
#include<algorithm>
#include<utility>
#include<stack>
#include<queue>
#include<vector>
#include<set>
#include<math.h>
#include<map>
#include<sstream>
#include<deque>
#include<unordered_map>
using namespace std;
typedef long long LL;
const int N = 1e3 + 5, M = 5e3 + 5;
int n, m;
int wf[M];
int h[N], e[M], ne[M], wt[M], idx;
double dist[N];
int q[N], vis[N], cnt[N];

void add(int a, int b, int c) {
	e[idx] = b, wt[idx] = c, ne[idx] = h[a], h[a] = idx++;
}

int check(double u) {
	memset(dist, 0, sizeof dist);
	memset(vis, 0, sizeof vis);
	memset(cnt, 0, sizeof cnt);
	int hh = 0, tt = 0;
	for (int i = 1; i <= n; i++) {
		q[tt++] = i;
		vis[i] = 1;
	}
	while (hh != tt) {
		int t = q[hh++];
		if (hh == N)hh = 0;
		vis[t] = 0;
		for (int i = h[t]; i != -1; i = ne[i]) {
			int j = e[i];
			if (dist[j] < dist[t] + wf[t] - u * wt[i]) {
				dist[j] = dist[t] + wf[t] - u * wt[i];
				cnt[j] = cnt[t] + 1;
				if (cnt[j] >= n)return 1;
				if (!vis[j]) {
					vis[j] = 1;
					q[tt++] = j;
					if (tt == N)tt = 0;
				}
			}
		}
	}
	return 0;
}


int main() {
	cin >> n >> m;
	memset(h, -1, sizeof h);
	for (int i = 1; i <= n; i++)scanf("%d", &wf[i]);
	for (int i = 1, a, b, c; i <= m; i++) {
		scanf("%d%d%d", &a, &b, &c);
		add(a, b, c);
	}

	double l = 0, r = 1e6, mid;
	while (r - l > 1e-4) {
		mid = (l + r) / 2;

		if (check(mid))l = mid;
		else r = mid;
	}
	printf("%.2lf\n", l);
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值