- 题目
- 题目大意
- 解法
- 参考代码
- 感想
ICPC2013南京邀请赛 E. Eloh 解题报告
题目
http://icpc.njust.edu.cn/Contest/194/Problem/E
题目大意
给出 \(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 个物品比值的连线是凸的,中间那个肯定不选
如果队列尾存在物品比值比当前物品比值低的,扔掉(从 \(M / V\) 大于任何 \(m_i / v_i\) 考虑)
如果队列尾存在和比值比当前物品和比值低的,扔掉(比值高和比值低的一定不差)
此时可以保证,队列中 \(m_i\) 递减,\(v_i\) 递减,为斜率优化创造了条件
如果队列尾连续 3 个点连线为凸,把倒数第二个扔了。保证队列中相邻点斜率递减
由于 \(M / V\) 递减,所以如果队头元素比队头+1元素优,队头扔了。此时队头元素最差
维护未选物品比值最优值:
如果队列尾存在和比值比当前物品和比值高的,扔掉。此时可以保证,队列中 \(m_i\) 递增,\(v_i\) 递增
如果队列尾连续 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 了。