• 分数规划 基本题型&模板


    分数规划 基本题型&模板

    定义

    01分数规划是这样的一类问题,有一堆物品,每一个物品有一个收益(ai),一个代价(bi),我们要求一个方案,选出k个物品,使选择的(sum{ai}/sum{bi})最大。

    基本做法是采用二分法,假设当前二分到的答案为x,那么:

    (sum{ai}/sum{bi}>=x)可以转化为:(sum{(ai-x*b_i)}>=0),所以每次check的时候将(a_i-x*b_i)进行排序,取前k个,判断他们的和是否大于等于0即可

    模板

    poj2976

    给出两个数组a和b,(a_i)代表这门课获得的成绩,(b_i)代表这门课的满分是多少,现在要求你舍弃掉k门课,使得平均绩点((100*sum{ai}/sum{bi}))最高。

    直接二分即可,注意是舍弃k门课,所以相当于选择n-k门课,另外输出需要四舍五入,可以直接利用.0lf进行四舍五入

    #include <math.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    #include <algorithm>
    #include <iostream>
    #include <limits>
    #include <map>
    #include <set>
    #include <vector>
    
    using namespace std;
    
    const int N = 1e6 + 5;
    typedef long long LL;
    int k, n;
    double a[N], b[N], c[N];
    
    const double eps = 1e-8;
    // 和0做比较
    int sgn(double x) {
        if (fabs(x) < eps) return 0;  // =0
        if (x < 0)
            return -1;  // < 0
        else
            return 1;  // > 0
    }
    
    bool check(double mid) {
        for (int i = 0; i < n; i++) c[i] = a[i] - mid * b[i];
        sort(c, c + n);
        double sum = 0;
        for (int i = n - 1; i >= k; i--) sum += c[i];  //选n-k项
        return (sgn(sum) >= 0);
    }
    
    int main() {
        while (scanf("%d%d",&n,&k)) {
            if ((n + k) == 0) break;
            for (int i = 0; i < n; i++) scanf("%lf", &a[i]);
            for (int i = 0; i < n; i++) scanf("%lf", &b[i]);
            double l = 0, r = 1;           //成绩最大是1,也就是满分
            while (r - l > 1e-8) {         // r与l的间隔小于1e-8
                double mid = (l + r) / 2;  // 不需要考虑加一的事情
                if (check(mid))
                    l = mid;
                else
                    r = mid;  // r和l都是mid}
            }
            printf("%.0lf
    ",l*100);//四舍五入
        }
        return 0;
    }
    

    例题

    最优比率生成环

    acwing361观光奶牛
    题意: 给定一张L个点、P条边的有向图,每个点都有一个权值f[i],每条边都有一个权值t[i]。求图中的一个环,使“环上各点的权值之和”除以“环上各边的权值之和”最大。输出这个最大值。 点数N~1e3, 边数M~5e3
    题解: 二分枚举答案,然后根据这个mid来重新构图,与(a_i-mid*b_i)类似,本题为$ f[t]-midw[i](的和是否大于0,那么将每个点的权值下放到边上,然后判断是否存在正环即可,也可以变个符号,判断是否存在负环:具体操作就是spfa时当对t点的所有出边进行更新的时候,原来的边权w[i],变为)midw[i] - f[t]$

    #include <bits/stdc++.h>
    
    using namespace std;
    
    int const N = 1e3 + 10, M = 5e5 + 10;
    double const eps = 1e-8;
    int e[M], ne[M], w[M], idx, h[N], n, m, cnt[N], st[N], f[N];
    double dist[N];
    
    // 建邻接表
    void add(int a, int b, int c) {
        e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
    }
    
    // spfa求负环(正环)
    bool spfa(double mid) {
        queue<int> q;
        
        memset(dist, 0x3f, sizeof dist);
        memset(cnt, 0, sizeof cnt);
        memset(st, 0, sizeof st);
        for (int i = 1; i <= n; i ++ ) {
            st[i] = true;
            q.push(i);
        }
        // dist[0] = 0, st[0] = 1, q.push(0);  如果希望能够正确求出dis
        while (q.size())  {
            int t = q.front();  // 取队首
            q.pop();  // 出队首
    
            st[t] = false;
    
            for (int i = h[t]; i != -1; i = ne[i]) {
                int j = e[i];
                if (dist[j] > dist[t] + w[i] * mid - f[t]) { // 这里是判断负环,如果是判正环:1.初始化写成memset(dis, 0xc0, sizeof dis), 2.更新条件写成dist[j] < dist[t] + w[i]
                    dist[j] = dist[t] + w[i] * mid - f[t];  // 边权发生改变
                    cnt[j] = cnt[t] + 1;  // 更新边数
                    if (cnt[j] >= n) return true;  // 如果j点到源点的边数大于等于n
                    if (!st[j]) {
                        q.push(j);
                        st[j] = true;
                    }
                }
            }
        }
        return false;
    }
    
    int main() {
        cin >> n >> m;
        memset(h, -1, sizeof h);
        for (int i = 1; i <= n; ++i) scanf("%d", &f[i]);
        for (int i = 1, a, b, c; i <= m; ++i) {
            scanf("%d %d %d", &a, &b, &c);
            add(a, b, c);
        }
        
        double l = 0, r = 1e9;
        while (r - l > eps) {
            double mid = (l + r) / 2;
            if (spfa(mid)) l = mid;
            else r = mid;
        }
        printf("%.2lf", l);
        return 0;
    }
    

    最优比率生成树

    acwing348 沙漠之王
    题意: 大卫希望渠道的总成本和总长度的比值能够达到最小。他只希望建立必要的渠道,为所有的村庄提供水资源,这意味着每个村庄都有且仅有一条路径连接至首都。他的工程师对所有村庄的地理位置和高度都做了调查,发现所有渠道必须直接在两个村庄之间水平建造。由于任意两个村庄的高度均不同,所以每个渠道都需要安装一个垂直的升降机,从而使得水能够上升或下降。建设渠道的成本只跟升降机的高度有关,换句话说只和渠道连接的两个村庄的高度差有关。需注意,所有村庄(包括首都)的高度都不同,不同渠道之间不能共享升降机。
    题解:
    要求找出一棵((a1/b1) + (a2/b2) +... + (an/bn))之和最大生成树
    我们设((a1/b1) + (a2/b2) +... + (an/bn) = mid)。那么对应于每一条边我们可以得到一条新边(ai-mid * bi),采用二分的方式枚举mid,如果得到的使用新边(ai-mid * bi)建成的最小生成树的权值之和为0,那么这个mid就是我们的答案;否则,找其他的mid

    #include <bits/stdc++.h>
    
    using namespace std;
    
    int const N = 2e3 + 10;
    
    int n;
    double dis[N], d[N][N], h[N][N]; // dis记录到最小生成树的最小距离,d数组记录两个点的最小距离,h数组记录两个点的最小高度
    double x[N], y[N], z[N]; // 记录每个点输入的位置
    bool vis[N]; // 判断每个点是否在最小生成树内
    
    // prime算法查找新边建立的最小生成树是否满足条件
    bool prime (double mid) {
        memset(dis, 0x3f, sizeof dis);
        memset(vis, 0, sizeof vis);
        double sum = 0; // 最小生成树的边权值和
        vis[1] = 1;
        
        // 把1号点放入集合后,计算和1号点相邻的所有点的距离
        for (int i = 2; i <= n; ++i)
            dis[i] = h[1][i] - mid * d[1][i];
        
        for (int i = 2; i <= n; ++i) {
            // 找出到最小生成树距离最小的那个点
            double mini = 0x3f3f3f3f;
            int u = -1;;
            for (int j = 2; j <= n; ++j) {
                if (!vis[j] && dis[j] < mini) {
                    mini = dis[j], u = j;
                }
            }
            
            // 放入最小生成树内
            vis[u] = 1;
            sum += dis[u];
            
            // 更新所有与u点相邻的点
            for (int j = 2; j <= n; ++j) {
                if (!vis[j] && dis[j] > h[u][j] - mid * d[u][j])
                    dis[j] = h[u][j] - mid * d[u][j];  // 用广义边更新
            }
        }
        
        // 判断是否满足条件
        if (sum >= 0) return false;  // mid取太小
        else return true;
    }
    
    int main() {
        while (scanf("%d", &n) != EOF && n) {
            memset(x, 0, sizeof x);
            memset(y, 0, sizeof y);
            memset(z, 0, sizeof z);
            memset(d, 0, sizeof d);
            memset(h, 0, sizeof h);
            for (int i = 1; i <= n; ++i) {
                cin >> x[i] >> y[i] >> z[i];
                // 建立一张完全图
                for (int j = 1; j < i; ++j) {
                    d[i][j] = d[j][i] = sqrt((x[i] - x[j]) * (x[i] - x[j]) + (y[i] - y[j]) * (y[i] - y[j]));  // 记录i点和前j个点的距离
                    h[i][j] = h[j][i] = fabs(z[i] - z[j]); // 记录高度差
                }
            }
            
            // 01分数规划,二分查找答案
            double l = 0, r = 1000.0, mid;
            while (r - l > 1e-6) {
                 mid = (l + r) / 2;
                 if (prime(mid)) r = mid; // 完全图采用prime算法
                 else l = mid;
            }
            printf("%.3f
    ", mid);
        }
        return 0;
    }
    

    树形背包01分数规划

    P1642 规划

    某地方有N个工厂,有N-1条路连接它们,且它们两两都可达。每个工厂都有一个产量值和一个污染值。现在工厂要进行规划,拆除其中的M个工厂,使得剩下的工厂依然连成一片且 总产量/总污染 的值最大。

    还是分数规划的基本套路,二分mid,然后去跑树形背包即可

    #include <bits/stdc++.h>
    
    using namespace std;
    
    const int N = 1e2 + 5;
    typedef long long LL;
    int n, m, a[N], b[N];
    vector<int> mp[N];
    double dp[N][N];
    
    const double eps = 1e-8;
    // 和0做比较
    int sgn(double x) {
        if (fabs(x) < eps) return 0;  // =0
        if (x < 0)
            return -1;  // < 0
        else
            return 1;  // > 0
    }
    int sz[N];
    void dfs(int now, int fa, double mid) {
        dp[now][0] = 0;
        sz[now] = 1;
        for (int i = 0; i < mp[now].size(); i++) {
            int son = mp[now][i];
            if (son == fa) continue;
            dfs(son, now, mid);
            sz[now] += sz[son];
            for (int j = min(m, sz[now]) - 1; j >= 0; j--)
                for (int k = 0; k <= min(j, sz[son]); ++k)
                    dp[now][j] = max(dp[now][j], dp[now][j - k] + dp[son][k]);
        }
        for (int i = min(m, sz[now]); i >= 1; --i)
            dp[now][i] = dp[now][i - 1] + 1.0 * a[now] - mid * b[now];
    }
    
    bool check(double mid) {
        for (int i = 0; i <= n;i++){
            for (int j = 0; j <= m; j++) dp[i][j] = -1e18;
        }
        dfs(1, 0, mid);
        for (int i = 1; i <= n; i++)
            if (sgn(dp[i][m]) >= 0) return true;
        return false;
    }
    
    int main() {
        cin >> n >> m;
        m = n - m;  //拆除m个,也就是选择n-m个
        for (int i = 1; i <= n; i++) cin >> a[i];
        for (int i = 1; i <= n; i++) cin >> b[i];
        for (int i = 0; i < n - 1; i++) {
            int x, y;
            cin >> x >> y;
            mp[x].push_back(y), mp[y].push_back(x);
        }
        double l = 0, r = 100000;
        while (r - l > 1e-8) {         // r与l的间隔小于1e-8
            double mid = (l + r) / 2;  // 不需要考虑加一的事情
            if (check(mid))
                l = mid;
            else
                r = mid;  
        }
        printf("%.1lf
    ", l);
        return 0;
    }
    
  • 相关阅读:
    NC 5的开发环境起不了客户端
    NC 6系初始化EJB
    ubuntu安装javaweb
    html 优化
    ubuntu
    jquery
    前端优化
    京东设计中心
    html cookie
    html5 use video camera
  • 原文地址:https://www.cnblogs.com/dyhaohaoxuexi/p/14536708.html
Copyright © 2020-2023  润新知