01分数规划:通常的问法是:在一张有 \(n\) 个点,\(m\) 条边的有向图中,每一条边均有其价值 \(v\) 与其代价 \(w\);求在图中的一个环使得这个环上所有的路径的权值和与代价和的比率最小\大。即求 \(\frac{\sum v}{\sum w}\) 的最小值\最大值。
通常的解法也是比较固定的,我们首先假设求最大值,最优的答案为 \(L\),\(L = \frac{\sum v}{\sum w}\)。接下来我们对于这个式子进行变形:
\(L * \sum w = \sum v\)
\(L * \sum w - \sum v = 0\)
注意到这里面的 \(L\) 是我们假设出来的最优解。若我们二分这个答案,设我们当前二分出来的答案为 \(R\),则考虑当 \(R < L\) 和 \(R > L\) 的时候会分别出现什么状况。当我们选定了一个 \(R\),一条边的 \(R * w - v\) 就是一个固定的值,不妨将它视作新的边权。而原式便是这些边权的和。若此时图中有负环,因为 \(R * w - v\) 随 \(R\) 的增大单调不减,说明可以将 \(R\) 的值设定得更大,有更有的解。存在零环说明此时的 \(R\) 恰好等于 \(L\),没有负环 & 零环说明不能取到当前值。
1.HNOI2009最小圈
裸的01分数规划,只是最大变成了最小。做法还是一样,将spfa转化为最长路判正环即可。
#include <bits/stdc++.h> using namespace std; #define maxn 10050 #define eps 0.0000000001 #define db double int n, m, cnp = 1, head[maxn]; db ans, dis[maxn]; bool vis[maxn], mark[maxn]; struct edge { int to, last; db co, w; }E[maxn]; void add(int u, int v, db w) { E[cnp].to = v, E[cnp].w = w; E[cnp].last = head[u], head[u] = cnp ++; } bool spfa(int u) { vis[u] = 1, mark[u] = 1; for(int i = head[u]; i; i = E[i].last) { int v = E[i].to; if(dis[v] < dis[u] + E[i].co) { dis[v] = dis[u] + E[i].co; if(vis[v] || spfa(v)) { vis[u] = 0; return 1; } } } vis[u] = 0; return 0; } bool check() { memset(mark, 0, sizeof(mark)); memset(dis, 0, sizeof(dis)); for(int i = 1; i <= n; i ++) if(!mark[i] && spfa(i)) return 1; return 0; } int main() { scanf("%d%d", &n, &m); for(int i = 1; i <= m; i ++) { int u, v; db w; scanf("%d%d%lf", &u, &v, &w); add(u, v, w); } db l = -100000, r = 100000; while(l + eps < r) { db mid = (l + r) / 2.0; for(int i = 1; i < cnp; i ++) E[i].co = mid - E[i].w; if(check()) ans = mid, r = mid; else l = mid; } printf("%.8lf\n", ans); return 0; }
2.APIO2017商旅
其实也是裸裸的一道题,建图的方式略有隐藏罢了。注意到两个点之间只能携带一种物品,我们将这些路径建成边连起来,然后套路即可。只不过因为这题我二分的是整数,所以要注意判零环。
#include <bits/stdc++.h> using namespace std; #define maxn 105 #define INF 99999999 int n, m, T, a[maxn][maxn * 10][2]; int R[maxn][maxn], val[maxn][maxn]; int ans, E[maxn][maxn], dis[maxn]; bool mark[maxn], vis[maxn]; int read() { int x = 0, k = 1; char c; c = getchar(); while(c < ‘0‘ || c > ‘9‘) { if(c == ‘-‘) k = -1; c = getchar(); } while(c >= ‘0‘ && c <= ‘9‘) x = x * 10 + c - ‘0‘, c = getchar(); return x * k; } void Floyd() { for(int k = 1; k <= n; k ++) for(int i = 1; i <= n; i ++) for(int j = 1; j <= n; j ++) R[i][j] = min(R[i][j], R[i][k] + R[k][j]); } void Build() { for(int i = 1; i <= n; i ++) for(int j = 1; j <= n; j ++) { if(R[i][j] >= INF) continue; for(int k = 1; k <= T; k ++) if(~a[i][k][0] && ~a[j][k][1]) { int v = a[j][k][1] - a[i][k][0]; if(v > val[i][j]) val[i][j] = v; } } } bool spfa(int u) { vis[u] = 1, mark[u] = 1; for(int i = 1; i <= n; i ++) { if(i == u || R[i][u] >= INF) continue; if(dis[i] > dis[u] + E[i][u] || (!mark[i] && dis[i] == dis[u] + E[i][u])) { dis[i] = dis[u] + E[i][u]; if(vis[i] || spfa(i)) { vis[u] = 0; return 1;} } else if(dis[i] == dis[u] + E[i][u] && vis[i]) { vis[u] = 0; return 1; } } vis[u] = 0; return 0; } bool check() { memset(dis, 0, sizeof(dis)); memset(mark, 0, sizeof(mark)); for(int i = 1; i <= n; i ++) if(!mark[i] && spfa(i)) return 1; return 0; } void work() { int l = 1, r = 5000; while(l <= r) { int mid = (l + r) >> 1; for(int i = 1; i <= n; i ++) for(int j = 1; j <= n; j ++) { if(R[i][j] >= INF) continue; E[i][j] = mid * R[i][j] - val[i][j]; } if(check()) ans = mid, l = mid + 1; else r = mid - 1; } } int main() { n = read(), m = read(), T = read(); for(int i = 1; i <= n; i ++) for(int j = 1; j <= 2 * T; j ++) if(j & 1) a[i][(j >> 1) + 1][0] = read(); else a[i][j >> 1][1] = read(); for(int i = 1; i <= n; i ++) for(int j = 1; j <= n; j ++) if(i != j) R[i][j] = INF; for(int i = 1; i <= m; i ++) { int x = read(), y = read(), t = read(); R[x][y] = min(R[x][y], t); } Floyd(); Build(); work(); printf("%d\n", ans); return 0; }
【算法】01分数规划 --- HNOI2009最小圈 & APIO2017商旅
原文:https://www.cnblogs.com/twilight-sx/p/9106319.html