算法:分数规划学习笔记
算法简介
分数规划是一个与二分紧密结合的算法技巧,关于它的习题通常可以得到如下式子:
\[\frac {\sum p_i \times w_i} {\sum q_i \times w_i} \ge {mid}
\]
然后通过变化得出:
\[({\sum p_i \times w_i} - {mid}\times{ q_i \times w_i}) \ge 0
\]
接着再根据实际情况建构对应模型解决问题。
例题讲解
P10505 Dropping Test
分数规划模板题,直接按照上面的的方式推导一下,然后令 \(c_i = a_i - mid \times b_i\),取最大的 \(n-k\) 个 \(c_i\) 求和检查即可。
#include <bits/stdc++.h>
using namespace std;
const double eps = 1e-6;
const int N = 1000 + 5;
int n, k, a[N], b[N];
double c[N];
bool chk(double mid) {
for(int i = 1; i <= n; i++) c[i] = a[i] - mid * b[i];
sort(c + 1, c + n + 1, greater<double>());
double ret = 0;
for(int i = 1; i <= n - k; i++) ret += c[i];
return ret >= 0;
}
int main() {
while(true) {
scanf("%d %d", &n, &k);
if(!n && !k) return 0;
for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
for(int i = 1; i <= n; i++) scanf("%d", &b[i]);
double l = 0, r = 1e12;
while(r - l > eps) {
double mid = (l + r) / 2.0;
if(chk(mid)) l = mid;
else r = mid;
}
printf("%.0lf\n", l * 100);
}
}
P4322 [JSOI2016] 最佳团体
分数规划结合背包 DP。
P2868 [USACO07DEC] Sightseeing Cows G
这是最优比率环模型的应用。
题意转化一下,就可以变成二分答案 \(mid\),检查是否有
\[\frac {\sum {F_i}}{\sum{E_i}} > mid
\]
其中 \(\sum E_i\) 表示每一条经过的边。
转化一下就有:
\[\sum({F_i}-mid \times {E_i}) > 0
\]
这样就可以直接边跑最长路边判是否有正环,但我实现时把式子取反跑判负环,两种写法均可通过。
#include <cstdio>
#include <queue>
using namespace std;
typedef long long i64;
const int N = 1000 + 5;
const int M = 5000 + 5;
const double eps = 1e-6;
int n, m, fun[N], hd[N], cnte, cnt[N];
double dis[N];
bool vis[N];
struct Edges {
int nxt, to, val;
} ed[M];
queue<int> q;
void addedge(int u, int v, int w) {
ed[++cnte] = (Edges) {hd[u], v, w}, hd[u] = cnte;
return;
}
bool chk(double mid) {
for(int i = 1; i <= n; i++) {
q.push(i);
dis[i] = 0;
vis[i] = 1;
cnt[i] = 1;
}
while(!q.empty()) {
int u = q.front(); q.pop();
vis[u] = 0;
for(int i = hd[u]; i; i = ed[i].nxt) {
int v = ed[i].to;
int w = ed[i].val;
if(dis[v] > dis[u] + mid * w - fun[u]) {
dis[v] = dis[u] + mid * w - fun[u];
if(!vis[v]) {
q.push(v);
vis[v] = 1;
cnt[v]++;
if(cnt[v] >= n) return true;
}
}
}
}
return false;
}
int main() {
scanf("%d %d", &n, &m);
for(int i = 1; i <= n; i++) scanf("%d", &fun[i]);
for(int u, v, w, i = 1; i <= m; i++) {
scanf("%d %d %d", &u, &v, &w);
addedge(u, v, w);
}
double l = 0, r = 1e6;
while(r - l > eps) {
double mid = (l + r) / 2.0;
if(chk(mid)) l = mid;
else r = mid;
}
printf("%.2lf", l);
return 0;
}
注意,本题需要在 \(T_i\ge1\) 的情况下才能保证重复经过一个点不优,从而使用该算法解决。

浙公网安备 33010602011771号