0/1分数规划模型
给定n个物品, 每个物品有两个权值ai, bi, 从这n个物品选取k(0 ≤ k ≤ n)个, 使得
\[\frac{\sum_{i=1}^{k}{a_i}}{\sum_{i=1}^{k}{b_i}} \]最大. 对于该类问题常用二分答案进行求解.
二分答案解决0/1分数规划模型
对于实数mid, 寻找是否有更优解使得
\[\frac{\sum_{i=1}^{k}{a_i}}{\sum_{i=1}^{k}{b_i}} \ge mid \]对上式进行变形
\[\sum_{i=1}^{k}{a_i} - \sum_{i=1}^{k}{b_i} * mid \ge 0 \]问题转化为判定是否存在一种选取方案满足上式, 若存在, 说明存在更优解, 令 l = mid, 否则 r = mid.
补充: 0/1分数规划模型涉及到选取方案问题, 常于背包, 贪心, 图论等结合.
例题
POJ2976 Dropping tests (0/1分数规划, 贪心)
思路:这是一道0/1分数规划板子题, 在选取方案时候可以进行贪心, 忽略掉ai/bi较小的k个.
// POJ2976 Dropping tests
#include <algorithm>
#include <cstring>
#include <iostream>
using namespace std;
const int maxn = 1005;
int n, k, a[maxn], b[maxn];
double c[maxn];
bool check(double mid) {
for (int i = 1; i <= n; ++i)
c[i] = a[i] - b[i] * mid;
sort(c + 1, c + 1 + n);
double sum = 0;
for (int i = k + 1; i <= n; ++i)
sum += c[i];
return sum >= 0;
}
int main() {
while (cin >> n >> k) {
if (!n && !k)
break;
for (int i = 1; i <= n; ++i)
cin >> a[i];
for (int i = 1; i <= n; ++i)
cin >> b[i];
double l = 0, r = 1;
while (r - l > 1e-6) {
double mid = (l + r) / 2;
if (check(mid))
l = mid;
else
r = mid;
}
printf("%.0f\n", l * 100);
}
return 0;
}
思路: 本题是最小生成树和0/1分数规划的结合.
// POJ2728 Desert King
#include <algorithm>
#include <cmath>
#include <cstring>
#include <iostream>
#include <vector>
using namespace std;
const int maxn = 1005;
int x[maxn], y[maxn], z[maxn], n;
double a[maxn][maxn], b[maxn][maxn];
double dist[maxn];
int vis[maxn];
double get_dist(int u, int v) {
int dx = abs(x[u] - x[v]);
int dy = abs(y[u] - y[v]);
return sqrt(dx * dx + dy * dy);
}
bool check(double mid) {
// Prim
memset(dist, 0x7f, sizeof dist);
memset(vis, 0, sizeof vis);
dist[1] = 0;
for (int i = 0; i < n - 1; ++i) {
int u = -1;
for (int j = 1; j <= n; ++j)
if (!vis[j] && (u == -1 || dist[u] > dist[j]))
u = j;
vis[u] = 1;
for (int v = 1; v <= n; ++v)
if (!vis[v] && dist[v] > a[u][v] - mid * b[u][v])
dist[v] = a[u][v] - mid * b[u][v];
}
double sum = 0;
for (int i = 2; i <= n; ++i)
sum += dist[i];
return sum <= 0;
}
int main() {
while (scanf("%d", &n) == 1 && n) {
for (int i = 1; i <= n; ++i)
scanf("%d%d%d", &x[i], &y[i], &z[i]);
for (int i = 1; i <= n; ++i)
for (int j = i; j <= n; ++j) {
a[i][j] = a[j][i] = abs(z[i] - z[j]);
b[i][j] = b[j][i] = get_dist(i, j);
}
double l = 0, r = 1e9;
while (r - l > 1e-6) {
double mid = (l + r) / 2;
if (check(mid))
r = mid;
else
l = mid;
}
printf("%.3f\n", l);
}
return 0;
}
P4377[USACO18OPEN] Talent Show G
思路 : 01背包和0/1分数规划的结合. 对n头牛做一遍01背包, 不同的是, 将大于m(m为背包容量, 题目中的W)的部分也统计到f[m]中.
// P4377[USACO18OPEN] Talent Show G
#include <algorithm>
#include <cstring>
#include <iostream>
using namespace std;
const int maxn = 255, maxm = 1005;
const double inf = 1e20;
int w[maxn], t[maxn], n, m;
double f[maxm];
bool check(double mid) {
for (int i = 0; i < maxm; ++i)
f[i] = -inf;
f[0] = 0;
for (int i = 1; i <= n; ++i) {
for (int j = m; j >= 0; --j) {
int x = min(j + w[i], m);
f[x] = max(f[x], f[j] + t[i] - mid * w[i]);
}
}
return f[m] >= 0;
}
int main() {
cin >> n >> m;
for (int i = 1; i <= n; ++i)
cin >> w[i] >> t[i];
double l = 0, r = 1e9;
for (int i = 1; i <= 100; ++i) {
double mid = (l + r) / 2;
if (check(mid))
l = mid;
else
r = mid;
}
printf("%d\n", (int)(l * 1000));
return 0;
}
思路:0/1分数规划和判负环结合.本题中的边权对应模型中ai, 边的条数对应bi, 求得是最小值, 只需把 ≥ 改为≤, 问题就转化为判定是否存在负环, 判断负环用spfa的dfs形式快些, 用bfs形式的spfa有些点会T.
// P3199 [HNOI2009]最小圈
#include <algorithm>
#include <cstring>
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
const int maxn = 3005;
struct Edge {
int to;
double w;
Edge(int _to = 0, double _w = 0) : to(_to), w(_w) {}
};
vector<Edge> e[maxn];
int n, m, cnt[maxn], vis[maxn];
double dist[maxn];
bool dfs(int u, double mid) {
vis[u] = 1;
for (int i = 0; i < e[u].size(); ++i) {
int v = e[u][i].to;
double w = e[u][i].w;
if (dist[v] > dist[u] + w - mid) {
dist[v] = dist[u] + w - mid;
if (vis[v] || dfs(v, mid))
return true;
}
}
vis[u] = 0;
return false;
}
bool spfa(double mid) {
memset(dist, 0, sizeof dist);
memset(vis, 0, sizeof vis);
for (int i = 1; i <= n; ++i)
if (dfs(i, mid))
return true;
return false;
}
int main() {
cin >> n >> m;
double l = 0, r = 0;
while (m--) {
int u, v;
double w;
cin >> u >> v >> w;
if (w < 0)
l += w;
else
r += w;
e[u].push_back(Edge(v, w));
}
while (r - l > 1e-10) {
double mid = (l + r) / 2;
if (spfa(mid))
r = mid;
else
l = mid;
}
printf("%.8f\n", l);
return 0;
}
参考:0/1 分数规划详解
标签:分数,dist,int,double,模型,mid,maxn,include,规划 From: https://www.cnblogs.com/lizsen/p/17045337.html