0/1分数规划模型

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;
}

POJ2728 Desert King

思路: 本题是最小生成树和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;
}

P3199 [HNOI2009]最小圈

思路: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;
}

POJ3621 Sightseeing Cows

思路: 0/1分数规划和负环结合. 将点权统计到为出边上, 问题转为为判定是否存在一个环使得 $$\sum_{i}^{}f[i] - mid * t[i] >= 0$$, 即判正环, 在求spfa负环的基础上改为求最长路径就变成判正环了.

// POJ3621 Sightseeing Cows
#include <algorithm>
#include <cstring>
#include <iostream>
#include <queue>
using namespace std;
const int maxn = 1005;
struct Edge {
    int to, w;
    Edge(int _to = 0, int _w = 0) : to(_to), w(_w) {}
};
vector<Edge> e[maxn];
int n, m, vis[maxn], f[maxn], cnt[maxn];
double dist[maxn];
bool check(double mid) {
    memset(cnt, 0, sizeof cnt);
    queue<int> q;
    for (int i = 1; i <= n; ++i)
        q.push(i), vis[i] = 1;
    while (q.size()) {
        int u = q.front();
        q.pop();
        vis[u] = 0;
        for (int i = 0; i < e[u].size(); ++i) {
            int v = e[u][i].to, w = e[u][i].w;
            if (dist[v] < dist[u] + f[u] - w * mid) {
                dist[v] = dist[u] + f[u] - w * mid;
                cnt[v] = cnt[u] + 1;
                if (cnt[v] >= n)
                    return true;
                if (!vis[v]) {
                    vis[v] = 1;
                    q.push(v);
                }
            }
        }
    }
    return false;
}
int main() {
    cin >> n >> m;
    for (int i = 1; i <= n; ++i)
        cin >> f[i];
    while (m--) {
        int u, v, w;
        cin >> u >> v >> w;
        e[u].push_back(Edge(v, w));
    }
    double l = 0, r = 1000;
    while (r - l > 1e-4) {
        double mid = (l + r) / 2;
        if (check(mid))
            l = mid;
        else
            r = mid;
    }
    printf("%.2f\n", l);
    return 0;
}

参考:0/1 分数规划详解

posted @ 2023-01-12 06:30  lizsen  阅读(126)  评论(0)    收藏  举报