小澳的葫芦
题意:
给出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; }