01 分数规划
01 分数规划指这样一类问题,对于每个元素,有 \(a\) 和 \(b\) 两种属性,要求按规则选出一些物品后 \(\dfrac{\sum a}{\sum b}\) 最大。
这样的问题可以二分答案 \(x\),看 \(\dfrac{\sum a}{\sum b}\) 是否可能 \(\ge x\),二分上界可以设成单个元素 \(\dfrac{a}{b}\) 的最大值。
化简不等式会得到 \(\sum a - x \sum b \ge 0\),也就是 \(\sum (a - x \cdot b) \ge 0\)。
这样一来,问题就转化为在该规则下选出的 \(a - x \cdot b\) 之和的最大值是否 \(\ge 0\)。
例题:P1404 平均数
分析:可以用 01 分数规划的思想看待这个问题,这里认为每个元素的 \(a\) 属性就是这个数的大小,\(b\) 属性就是数量,也就是 \(1\),希望 \(\dfrac{\sum a}{\sum b}\) 尽可能大。
二分答案后转化为选出的 \(a - x \cdot b\) 之和大于等于 \(0\),也就是 \(a - x\) 之和大于等于 \(0\),就是把所有元素减 \(x\) 以后让选出的数之和大于等于 \(0\)。
注意精度问题,可以在刚开始把每个数都乘以 \(1000\),然后进行二分,整体时间复杂度为 \(O(n \log a)\)。
参考代码
#include <cstdio>
#include <algorithm>
using std::max;
using std::min;
using ll = long long;
const int N = 100005;
int a[N], n, m;
ll sum[N], pre[N];
bool check(int x) {
for (int i = 1; i <= n; i++) {
sum[i] = sum[i - 1] + a[i] - x;
pre[i] = min(pre[i - 1], sum[i]);
}
for (int i = m; i <= n; i++)
if (sum[i] - pre[i - m] >= 0) return true;
return false;
}
int main()
{
scanf("%d%d", &n, &m);
int l = 0, r = 0, ans = 0;
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]); a[i] *= 1000;
r = max(r, a[i]);
}
while (l <= r) {
int mid = (l + r) / 2;
if (check(mid)) {
l = mid + 1; ans = mid;
} else {
r = mid - 1;
}
}
printf("%d\n", ans);
return 0;
}
习题:P1419 寻找段落
解题思路
在上一题的基础上多了长度在 \([S, T]\) 的限制。用单调队列维护前缀和的区间最小值。
参考代码
#include <cstdio>
#include <deque>
using std::deque;
const int N = 100005;
const double INF = 1e4;
const double EPS = 1e-5;
int n, s, t, a[N];
double sum[N];
bool check(double x) {
for (int i = 1; i <= n; i++) sum[i] = sum[i - 1] + (a[i] - x);
deque<int> dq;
for (int i = s; i <= n; i++) {
while (!dq.empty() && dq.front() < i - t) dq.pop_front();
while (!dq.empty() && sum[dq.back()] > sum[i - s]) dq.pop_back();
dq.push_back(i - s);
if (sum[i] - sum[dq.front()] > -EPS) return true;
}
return false;
}
int main()
{
scanf("%d%d%d", &n, &s, &t);
double l = -INF, r = INF;
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
}
while (r - l > EPS) {
double mid = (l + r) / 2;
if (check(mid)) {
l = mid;
} else {
r = mid;
}
}
printf("%.3f\n", l);
return 0;
}