做网站切图是什么意思搜索软件使用排名
一、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×wi∑i=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×wi∑ai×wi>M∑ai×wi−M×i=1∑nbi×wi∑wi×(ai−Mbi)
那么只要求出不等号左边的式子的最大值就行了。如果最大值比 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) >= Msum 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;
}