首页 > 代码库 > 小澳的葫芦

小澳的葫芦

题意:

给出n(<=200)个点,m(<=2000)条边的有向无环图,每条边有个边权,问从1号节点到n号节点的路径上的 权值和 与 点数的最小比值

 

题解:

这个题有两种做法:

一、

定义dis[u][k]表示从1号节点到达u号点经过k个节点的最小权值和,用SPFA就可以完成,最后直接循环找一遍dis[n][k] / k的最小值

 

二、

但是以上只适用于点很少的情况,点多的情况一般采用以下套路~

另建一个起点0,连接一条0到1长度为0的边,就此将问题转化为长度和边数最小比值。这个问题的求解需要分数规划。

假设答案为ans,对于任意一条由k条边组成的路径,有:
由 (w1 + w2 + w3 + … + wk) / k >= ans
可以得到 : (w1 + w2 + w3 + … + wk) >= ans * k
即(w1 - ans) + (w2 - ans) + (w3 - ans) + … + (wk - ans) >= 0

于是就很容易想到

二分答案x,每次将每一条边的权值减去x求最短路,判断1~n的最短路是否大于0

#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;

const int N = 2e2 + 7;
const int M = 2e3 + 7;
const double eps = 1e-9;

int head[N], n, m, ecnt, inq[N];
double dis[N];

struct edge {
	int v,nxt;
	double w;
}e[M];

void adde (int u, int v, int w) {
	e[ecnt].v = v;
	e[ecnt].w = w;
	e[ecnt].nxt = head[u];
	head[u] = ecnt++;
}

int check (double x) {
	for (int i = 0; i < ecnt; ++i) e[i].w -= x;
	for (int i = 1; i <= n; ++i) dis[i] = 1e9 + 7, inq[i] = 0;
	queue <int> q;
	q.push(0), dis[0] = 0, inq[0] = 1;
	while (!q.empty()) {
		int u = q.front();
		q.pop();
		inq[u] = 0;
		for (int it = head[u]; it != -1; it = e[it].nxt) {
			int v = e[it].v;
			if (dis[v] > dis[u] + e[it].w) {
				dis[v] = dis[u] + e[it].w;
				if (!inq[v]) {
					q.push(v);
					inq[v] = 1;
				}
			}
		}
	}
	for (int i = 0; i < ecnt; ++i) e[i].w += x;
	return dis[n] - eps < 0;
}

int main () {
	scanf ("%d%d", &n, &m);
	memset (head, -1, sizeof head);
	adde (0, 1, 0);
	for (int i = 1; i <= m; ++i) {
		int u, v, w;
		scanf ("%d%d%d", &u, &v, &w);
		adde (u, v, w);
	}
	double l = 0, r = M;
	while (l + eps < r) {
		double mid = (l + r) / 2.0;
		if (check (mid)) r = mid;
		else l = mid;
	}
	printf ("%.3lf\n", l);
	return 0;
}

  

小澳的葫芦