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 个物品比值的连线是凸的,中间那个肯定不选


    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元素比队尾元素优,队尾扔了。此时队尾元素最优


    也就是说这部分不需要队列只要一个栈就可以了


参考代码


#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了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值