NC17315 背包

题目地址:

https://ac.nowcoder.com/acm/problem/17315

基本思路:

这道题我们要分类讨论,先看为奇数的情况:

我们考虑先按照物品价值排序物品,那么要找最大中位数就是确定一个位置,如果这个位置能作为中位数,那么也就是说它左边的个物品的最小重量,右边的个物品的最小重量和自身重量的和一定要小于等于背包容量。

我们考虑如何求左边的个物品的最小重量,和右边的个物品的最小重量,这个实际上我们只要使用优先队列优化一下前后缀和,在达到数量的情况下,每次去掉队列里最大的元素就行了。

但是我们看为偶数的情况:

这个情况下我们同样先确定左边个物品的最小重量,和右边个物品的最小重量。

但是我们就没办法枚举这个中间位置了,因为中间位置是有两个的,但是这时候实际上我们枚举第一个位置然后二分第二个位置就行了。因为第二个位置对应的数越大中位数肯定也是单调增大的,而且位置越往后最小重量也是递减的,所以符合二分条件,因此二分成立。

ps. 注意一下偶数的情况,算中间两个数平均值时直接向下取整。

参考代码:

#pragma GCC optimize(2)
#pragma GCC optimize(3)
#include <bits/stdc++.h>
using namespace std;
#define IO std::ios::sync_with_stdio(false)
#define int long long
#define rep(i, l, r) for (int i = l; i <= r; i++)
#define per(i, l, r) for (int i = l; i >= r; i--)
#define mset(s, _) memset(s, _, sizeof(s))
#define pb push_back
#define pii pair <int, int>
#define mp(a, b) make_pair(a, b)
#define INF (int)1e18

inline int read() {
  int x = 0, neg = 1; char op = getchar();
  while (!isdigit(op)) { if (op == '-') neg = -1; op = getchar(); }
  while (isdigit(op)) { x = 10 * x + op - '0'; op = getchar(); }
  return neg * x;
}
inline void print(int x) {
  if (x < 0) { putchar('-'); x = -x; }
  if (x >= 10) print(x / 10);
  putchar(x % 10 + '0');
}

const int maxn = 1e5 + 10;
struct Node{
    int val,w;
    bool operator < (const Node & no) const {
      return val < no.val;//按照价值排序;
    }
}a[maxn];
int per[maxn],suf[maxn];
int v,n,m;
signed main() {
  IO;
  cin >> v >> n >> m;
  rep(i, 1, n) cin >> a[i].val >> a[i].w;
  sort(a + 1, a + 1 + n);
  priority_queue<int> que;
  int l = m / 2, r = m / 2;
  if (m % 2 == 0) l--; // 偶数情况左边是m/2-1个;
  //优先队列优化前缀;
  rep(i, 1, n) {
    que.push(a[i].w);
    per[i] = per[i - 1] + a[i].w;
    if (que.size() > l) per[i] -= que.top(), que.pop();
  }
  //优先队列优化后缀;
  while (!que.empty()) que.pop();
  per(i, n, 1) {
    que.push(a[i].w);
    suf[i] = suf[i + 1] + a[i].w;
    if (que.size() > r) suf[i] -= que.top(), que.pop();
  }
  if (m % 2 == 1) {//奇数情况;
    int ans = 0;
    rep(i, l + 1, n - r) {
      if (per[i - 1] + suf[i + 1] + a[i].w <= v) ans = a[i].val;
    }
    cout << ans << '\n';
  } else {//偶数情况;
    int ans = 0;
    rep(i, l + 1, n - r) {
      //二分第二个位置;
      int nl = i + 1, nb = n - r + 1, pos = 0;
      while (nl <= nb) {
        int mid = (nl + nb) >> 1;
        if (per[i - 1] + suf[mid] + a[i].w <= v) pos = mid, nl = mid + 1;
        else nb = mid - 1;
      }
      if (pos > i) ans = max(ans, a[i].val + a[pos].val);
    }
    //注意一下这个要直接向下取整;
    cout << ans / 2 << '\n';
  }
  return 0;
}