1. 题目
  2. 题目大意
  3. 解法
  4. 参考代码
  5. 感想

ICPC2013南京邀请赛 E. Eloh 解题报告

题目

题目大意

给出 $n$ 个物品的质量 $m_i$ 和体积 $v_i$,

手机上的澳门永利真的假的选出若干个使得 $sum {m_j} / sum {v_j}, (1 <= j <= n - s)$ 达到最大值。

输出不选的个数 $s$ 和不选的物品编号。

解法

01分数规划、单调队列、栈

手机上的澳门永利真的假的按 $m_i / v_i$ 排序,枚举 $s$,并维护以下两个集合中的最值即可。

  • 手机上的澳门永利真的假的维护已选物品中比值最差值:

    按比值从高到低把每个物品加入队列

    手机上的澳门永利真的假的设当前已选物品的和比值为 $M / V = sum {m_j} / sum {v_j}$

    手机上的澳门永利真的假的类似斜率优化,物品 $i$ 暂时劣于物品 $j$,且 $m_i / v_i < m_j / v_j$ => $(m_i - m_j) / (v_i - v_j) < M / V$

    手机上的澳门永利真的假的把每个物品比值看成平面上的点,如果连续 3 个物品比值的连线是凸的,中间那个肯定不选

    1. 如果队列尾存在物品比值比当前物品比值低的,扔掉(从 $M / V$ 大于任何 $m_i / v_i$ 考虑)

    2. 如果队列尾存在和比值比当前物品和比值低的,扔掉(比值高和比值低的一定不差)

      此时可以保证,队列中 $m_i$ 递减,$v_i$ 递减,为斜率优化创造了条件

    3. 如果队列尾连续 3 个点连线为凸,把倒数第二个扔了。保证队列中相邻点斜率递减

    4. 由于 $M / V$ 递减,所以如果队头元素比队头+1元素优,队头扔了。此时队头元素最差

  • 维护未选物品比值最优值:

    1. 如果队列尾存在和比值比当前物品和比值高的,扔掉。此时可以保证,队列中 $m_i$ 递增,$v_i$ 递增

    2. 手机上的澳门永利真的假的如果队列尾连续 3 个点连线为凹,把倒数第二个扔了。保证了队列中相邻点斜率递减

    3. 手机上的澳门永利真的假的由于 $M / V$ 递增,所以如果队尾-1元素比队尾元素优,队尾扔了。此时队尾元素最优

    手机上的澳门永利真的假的也就是说这部分不需要队列只要一个栈就可以了

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
#include <iostream>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
#define N 200002
typedef long long ll;
struct point
{
ll x, y;
point() {}
point(ll x, ll y): x(x), y(y) {}
point operator-(const point & i) const
{
return point(x - i.x, y - i.y);
}
ll operator*(const point & i) const
{
return x * i.y - y * i.x;
}
bool operator<(const point & i) const
{
return (*this) * i > 0;
}
} a[N], b[N];
int q[N], p[N];
int n;

vector<int> solve()
{
vector<int> ans;
ll sx = 0, sy = 0;
sort(a + 1, a + n + 1);
for (int i = 1; i < n; ++i)
{
sx += a[n - i + 1].x;
sy += a[n - i + 1].y;
b[n - i] = point(sx, sy);
}
memset(p, 0, sizeof(p));
memset(q, 0, sizeof(q));
int t = 0;
for (int i = 1; i < n; ++i)
{
while (t && a[q[t]].x >= a[i].x)
--t;
while (t > 1 && (a[i] - a[q[t]]) * (a[q[t - 1]] - a[q[t]]) >= 0)
--t;
q[++t] = i;
while (t > 1 && b[i] * (a[q[t]] - a[q[t - 1]]) <= 0)
--t;
p[i] = q[t];
}
int h = 1;
t = 0;
for (int i = n - 1; i; --i)
{
while (h <= t && (a[q[t]].x <= a[i + 1].x || a[q[t]].y <= a[i + 1].y))
--t;
while (h < t && (a[i + 1] - a[q[t]]) * (a[q[t - 1]] - a[q[t]]) >= 0)
--t;
q[++t] = i + 1;
while (h < t && b[i] * (a[q[h + 1]] - a[q[h]]) <= 0)
++h;
if (b[i] * (a[p[i]] - a[q[h]]) > 0)
ans.push_back(i);
}
return ans;
}

int main()
{
while (cin >> n && n)
{
for (int i = 1; i <= n; ++i)
cin >> a[i].y >> a[i].x;
vector<int> ans = solve();
cout << ans.size() << endl;
for (int i = ans.size() - 1; i >= 0; --i)
cout << ans[i] << endl;
}
return 0;
}

感想

手机上的澳门永利真的假的其实这是一道比较经典复杂的01分数规划的题,除了用单调队列、栈来做,似乎用平衡树会有更好的效果。

比赛时看懂题意之后就觉得跟USACO某次月赛的某道题很像,然后就1Y了。