当前位置: 首页 > news >正文

01分数规划,二分法,题目练习

一、01分数规划

1.1 01分数规划

01分数规划用来求一个分式的极值。模型如下:

给出 a i a_i ai b i b_i bi,求一组 w i ∈ { 0 , 1 } w_i \in \{ 0, 1 \} wi{0,1}最小化或最大化
∑ i = 1 n a i × w i ∑ i = 1 n b i × w i \frac{\sum_{i=1}^{n}a_i \times w_i}{\sum_{i=1}^{n}b_i \times w_i} i=1nbi×wii=1nai×wi

1.2 二分法

分数规划问题的通用方法是二分。

假设我们要求最大值。二分一个最大值M,那么有:
∑ a i × w i ∑ i = 1 n b i × w i > M ∑ a i × w i − M × ∑ i = 1 n b i × w i ∑ w i × ( a i − M b i ) \begin{align} & \frac{\sum_{}^{}a_i \times w_i}{\sum_{i=1}^{n}b_i \times w_i} \gt M \\ & \sum_{}^{}a_i \times w_i - M\times\sum_{i=1}^{n}b_i \times w_i \\ & \sum_{}^{}w_i\times (a_i - Mb_i) \end{align} i=1nbi×wiai×wi>Mai×wiM×i=1nbi×wiwi×(aiMbi)
那么只要求出不等号左边的式子的最大值就行了。如果最大值比 0 要大,说明 mid 是可行的,否则不可行。

有时候01分数规划也会和网络流结合。详见最小割问题合集,最大权闭合图,最大密度子图,最小权点覆盖,最大权独立子图,OJ练习,代码详解-CSDN博客

1.3 题目练习

1.3.1 P10505 Dropping Test

原题链接

P10505 Dropping Test

思路分析

模板题,分式的最大值,最后 * 100 输出即可。

AC代码

#include <bits/stdc++.h>

using i64 = long long;
using u64 = unsigned long long;
using u32 = unsigned;
using u128 = unsigned __int128;

constexpr double eps = 1E-6;

int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);

    int n, k;

    while (std::cin >> n >> k, n) {
        std::vector<int> a(n), b(n);
        for (int i = 0; i < n; ++ i) {
            std::cin >> a[i];
        }     
        for (int i = 0; i < n; ++ i) {
            std::cin >> b[i];
        }

        auto check = [&](double m) -> bool{
            std::vector<double> st;
            for (int i = 0; i < n; ++ i) {
                st.push_back(a[i] - b[i] * m);
            }
            std::ranges::sort(st);
            double s = 0;
            for (int i = k; i < n; ++ i) {
                s += st[i];
            }
            return s >= 0;
        };

        double lo = 0, hi = 1;
        while (lo + eps < hi) {
            double x = (lo + hi) / 2;            
            if (check(x)) {
                lo = x;
            } else {
                hi = x;
            }
        }

        std::cout << std::round(lo * 100) << '\n';
    }

    return 0;
}
1.3.2 P4377 [USACO18OPEN] Talent Show G

原题链接

P4377 [USACO18OPEN] Talent Show G

思路分析

sum(t) / sum(w) >= m,m 为二分最大值

那么 sum(t - w * m) >= 0,我们如何得到sum(w) >= W下最大的sum(t - w * m)?——01背包

AC代码

#include <bits/stdc++.h>

using i64 = long long;
using u64 = unsigned long long;
using u32 = unsigned;
using u128 = unsigned __int128;

constexpr double eps = 1E-4;

int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);

    int n, W;
    std::cin >> n >> W;
    std::vector<int> w(n), t(n);
    for (int i = 0; i < n; ++ i){
        std::cin >> w[i] >> t[i];
    }

    /*
    sum(t) / sum(w) >= M
    sum t - wM >= 0
    */

    auto check = [&](double m) -> bool{
        std::vector<double> f(W + 1, std::numeric_limits<double>::lowest() / 2);
        f[0] = 0;
        for (int i = 0; i < n; ++ i) {
            for (int j = W; j >= 0; -- j) {
                int nj = std::min(W, j + w[i]);
                f[nj] = std::max(f[nj], f[j] + t[i] - w[i] * m);
            }
        }
        return f[W] >= 0;
    };

    double lo = 0, hi = std::accumulate(t.begin(), t.end(), 0);

    while (lo + eps <= hi) {
        double x = (lo + hi) / 2;
        if(check(x)) {
            lo = x;
        } else {
            hi = x;
        }
    }

    std::cout << (int)(1000 * lo) << '\n';

    return 0;
}
1.3.3 Desert King

原题链接

Desert King

思路分析

01 分数规划 + 最小生成树

l(i, j) 为 节点 i 和 j 之间的欧氏距离,h(i, j) 为二者高度差

那么二分最大比率 m,有 sum(l) / sum(h) <= m,即 sum(l - m * h) <= 0

我们对于 节点i,j,以 l(i, j) - m * h(i, j) 为边权求最小生成树即可。

由于是稠密图,所以 直接用prim算法

AC代码

#include <iostream>
#include <vector>
#include <cmath>
#include <cassert>
#include <iomanip>

const double eps = 1E-5;

int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);

    int n;
    while (std::cin >> n, n) {
        std::vector<int> x(n), y(n), z(n);
        for (int i = 0; i < n; ++ i) {
            std::cin >> x[i] >> y[i] >> z[i];
        }

        // sum(h) / sum(l) <= k
        // sum(h - l * k)

        std::vector<std::vector<double>> l(n, std::vector<double>(n));
        std::vector<std::vector<int>> h(n, std::vector<int>(n));

        for (int i = 0; i < n; ++ i) {
            for (int j = 0; j < n; ++ j) {
                l[i][j] = sqrt(1.0 * (x[i] - x[j]) * (x[i] - x[j]) + 1.0 * (y[i] - y[j]) * (y[i] - y[j]));
                h[i][j] = abs(z[i] - z[j]);
            }
        }

        double lo = 0, hi = 1000;
        while (lo + eps <= hi) {
            double m = (lo + hi) / 2;

            double res = 0;
            std::vector<bool> st(n);
            std::vector<double> min(n);
            for (int i = 0; i < n; ++ i) {
                min[i] = h[0][i] - m * l[0][i];
            }
            min[0] = 0;
            st[0] = true;
            for (int k = 0; k < n - 1; ++ k) {
                int mini = -1;
                for (int i = 0; i < n; ++ i) {
                    if (!st[i] && (mini == -1 || min[mini] > min[i])) {
                        mini = i;
                    }
                }
                assert(mini != -1);
                res += min[mini];
                st[mini] = true;
                for (int i = 0; i < n; ++ i) {
                    if (!st[i]) {
                        min[i] = std::min(min[i], h[mini][i] - m * l[mini][i]);
                    }
                }
            }

            if (res <= eps) {
                hi = m;
            } else {
                lo = m;
            }
        }

        std::cout << std::fixed << std::setprecision(3) << hi << '\n';
    }

    return 0;
}
1.3.4 P3199 [HNOI2009] 最小圈

原题链接

P3199 [HNOI2009] 最小圈

思路分析

二分最小值x,那么原式变形为 Σ w - x <= 0

我们在原图以 w - x 为新边权找负环即可

我用的是bellman-ford,不过本题不卡spfa,如果换spfa可以获得更短的运行时间

时间复杂度:O(nm logW)

AC代码

#include <bits/stdc++.h>

using i64 = long long;

int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);

    int n, m;
    std::cin >> n >> m;

    std::vector<std::vector<std::pair<int, double>>> adj(n);
    for (int i =0; i < m; ++ i) {
        int u, v;
        double w;
        std::cin >> u >> v >> w;
        -- u, -- v;
        adj[u].emplace_back(v, w);
    }

    std::vector<double> dis(n);

    auto Bellman_Ford = [&](double x) -> bool {
        dis.assign(n, std::numeric_limits<double>::max());
        dis[0] = 0;

        bool flag = false;

        for (int i = 0; i < n; i++)
        {
            flag = false;
            for (int u = 0; u < n; ++ u)
            {
                if (dis[u] == std::numeric_limits<double>::max())
                    continue;
                for (auto &[v, w] : adj[u])
                {
                    if (dis[v] > dis[u] + w - x) {
                        dis[v] = dis[u] + w - x;
                        flag = true;
                    }
                }
            }
            if (!flag)
                return false;
        }

        return flag;
    };

    double lo = -1E7, hi = 1E7;
    constexpr double eps = 1E-10;
    while (lo + eps <= hi) {
        double x = (lo + hi) / 2;
        if (Bellman_Ford(x)) {
            hi = x;
        } else{
            lo = x;
        }
    }

    std::cout << std::fixed << std::setprecision(8) << hi << '\n';

    return 0;
}
1.3.5 JSOI2016] 最佳团体

原题链接

P4322 [JSOI2016] 最佳团体

思路分析

树形背包 + 01分数规划

考虑从推荐人向被推荐人连有向边

二分最大值 M

那么有 Σ p - s * M >= 0

p - s * M 就是新的点权

我们在树上跑树形背包即可。 f(u, v) 为 以u 为根的子树,招募 v 个人的最大收益,需要一次dfs来预处理子树大小

为了减小常数,我们可以在第一次dfs的时候求dfs序列,然后在 dfs序列上跑树形背包。

AC代码

#include <bits/stdc++.h>

using i64 = long long;

constexpr double eps = 1E-6;

int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);

    // sum(p) / sum(s)

    int k, n;
    std::cin >> k >> n;
    ++ k;
    std::vector<int> s(n + 1), p(n + 1), r(n + 1), siz(n + 1);
    std::vector<std::vector<int>> adj(n + 1);

    for (int i = 1; i <= n; ++ i) {
        std::cin >> s[i] >> p[i] >> r[i];
        adj[r[i]].push_back(i);
    }

    std::vector<int> seq(n + 1);
    int cur = 0;
    auto dfs = [&](auto &&self, int u) -> void{
        seq[cur ++] = u;
        siz[u] = 1;
        for (int v : adj[u]) {
            self(self, v);
            siz[u] += siz[v];
        }
    };

    dfs(dfs, 0);

    auto check = [&](double m) -> bool{
        std::vector<std::vector<double>> f(n + 2, std::vector<double>(k + 1, std::numeric_limits<double>::lowest() / 2));
        f[n + 1][0] = 0;
        for (int i = n; i >= 0; -- i) {
            f[i][0] = 0;
            for (int j = k; j >= 1; -- j) {
                f[i][j] = std::max(f[i + 1][j - 1] + (p[seq[i]] - s[seq[i]] * m), f[i + siz[seq[i]]][j]);
            }
        }
        return f[0][k] >= 0;
    };

    double lo = 0, hi = std::accumulate(s.begin(), s.end(), 0.0);
    while (lo + eps <= hi) {
        double x = (lo + hi) / 2;
        if (check(x)) {
            lo = x;
        } else {
            hi = x;
        }
    }

    std::cout << std::fixed << std::setprecision(3) << hi << '\n';

    return 0;
}

相关文章:

  • rust学习笔记18-迭代器
  • 网络安全威胁与防护措施(上)
  • “锈化”Python:用Rust重塑Python生态的六大工具深度解析
  • 床头灯3000词:《傲慢与偏见》(Pride and Prejudice)阅读(英语学习)记录
  • 用爬虫解锁 Shopee 店铺商品数据,开启电商新洞察
  • springboot项目,mapper.xml里面,jdbcType报错 已解决
  • day 5 寄存器 时钟 堆栈
  • 蓝桥杯 之 暴力回溯
  • 分开6年,移居美国的吴秀波和被送进监狱的小三陈昱霖,如今都怎么样了?
  • springboot项目引用外部jar包,linux部署后启动失败,找不到jar包
  • Jvm运行时数据区有哪些
  • SpringCache @Cacheable 在同一个类中调用方法,导致缓存不生效的问题及解决办法
  • 2025年渗透测试面试题总结- PingCAP安全工程师(题目+回答)
  • 蓝桥杯十天冲刺-day1(日期问题)
  • 线程互斥量和信号量的使用(未完成)
  • 基于SpringBoot的社区/物业管理系统
  • Vala编程语言教程-语言元素
  • Netty源码—2.Reactor线程模型一
  • Microchip AN1477中关于LLC数字补偿器的疑问
  • Python列表2
  • 体坛联播|C罗儿子完成国家队首秀,德约结束与穆雷合作
  • 中华人民共和国和巴西联邦共和国关于强化携手构建更公正世界和更可持续星球的中巴命运共同体,共同维护多边主义的联合声明
  • 美国三大指数全线高开:纳指涨逾4%,大型科技股、中概股大涨
  • 上海现有超12.3万名注册护士,本科及以上学历占一半
  • 科普|“小”耳洞也会引发“大”疙瘩,如何治疗和预防?
  • 【社论】人工智能将为教育带来什么