题目描述
给定 对数 ,现删去 对数。问有多少个满足条件的 ,使得存在另外一种删除 对的方式,使得剩余 比删除 最小的 对 剩下的 更大。
题解
首先,如果有另外一种删除方式使得 比删除 最小的 对更大,那么一定存在:在 最小的 对当中,抽出一个保留;再删除一个 较大的,剩下的 更大。
证明:把它们保留的共同元素提出来不管。对于剩下保留的不同元素,在更优方案中一定有一个保留价值比原先保留方案的其中一个大的元素;否则新方案不会比原先的方案大。
考虑用函数来描述“保留价值”。设目前答案值为 ,那么将第 个元素的保留价值表示为 。显然: 则保留 有正收益, 则保留 有负收益;若 ,则保留 比保留 更有价值。
既然我们保留前 个,那么若 当中有一个元素 保留价值比 其中一个元素保留价值 更高,则保留 删除 比原先的删除 保留 的最终值更大。
即
则此时 满足条件。注意此处的 和 是剩余的前 项的和。
考虑快速求 和 。
先返回原来的 。比较两决策点 , 比 不劣当且仅当 。由于 随着 的增大而减小(不断加入比值更小的分数), 始终不劣于 。因此, 实际取到最小值的 随着 的增大而增大。
同理, 实际取到最大值的 也随着 的增大而增大。
因此,决策点 总是单调递增。考虑决策单调性分治。以求 为例:
- 令 为求 的 ,其中最优决策点 。
- 为 暴力在 中找出最优的 。注意 不能大于 (由定义)。
- 左边的 最优决策点位置一定小于等于 ,右侧的一定大于等于 。
- 递归计算 和 。
的计算同理。
时间复杂度 。
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
using LL = long long;
const LL kMaxN = 5e4 + 5;
struct {
LL t, p;
} a[kMaxN];
LL f[kMaxN], g[kMaxN], st[kMaxN], sp[kMaxN], n;
vector<LL> ans;
void F(LL l, LL r, LL wyy, LL zrr) {
if (l > r) {
return;
}
LL m = (l + r) / 2, best = wyy;
f[m] = 1e18;
for (LL i = wyy; i <= min(m, zrr); i++) {
LL val = sp[m] * a[i].t - st[m] * a[i].p;
if (val < f[m]) {
f[m] = val;
best = i;
}
}
F(l, m - 1, wyy, best);
F(m + 1, r, best, zrr);
}
void G(LL l, LL r, LL wyy, LL zrr) {
if (l > r) {
return;
}
LL m = (l + r) / 2, best = zrr;
g[m] = -1e18;
for (LL i = zrr; i >= max(m + 1, wyy); i--) {
LL val = sp[m] * a[i].t - st[m] * a[i].p;
if (val > g[m]) {
g[m] = val;
best = i;
}
}
G(l, m - 1, wyy, best);
G(m + 1, r, best, zrr);
}
int main() {
cin.tie(0)->sync_with_stdio(0);
cin >> n;
for (LL i = 1; i <= n; i++) {
cin >> a[i].t >> a[i].p;
}
sort(a + 1, a + n + 1, [](auto a, auto b) {
return a.t * b.p > a.p * b.t;
});
for (LL i = 1; i <= n; i++) {
st[i] = st[i - 1] + a[i].t;
sp[i] = sp[i - 1] + a[i].p;
}
F(1, n - 1, 1, n);
G(1, n - 1, 1, n);
for (LL i = 1; i < n; i++) {
if (f[i] < g[i]) {
ans.push_back(n - i);
}
}
reverse(ans.begin(), ans.end());
cout << ans.size() << '\n';
for (LL i : ans) {
cout << i << '\n';
}
return 0;
}