QOJ #5076. Prof. Pang and Ants 题解

Description

在庞教授的大房子边上,有一群包含 \(m\) 只蚂蚁的蚁群,居住在有 \(n\) 个洞口的洞穴里。 它们会外出寻找食物。食物在庞教授的大冰箱里,蚂蚁们试图从里面偷出食物来。

特别的, 一只蚂蚁需要 \(1\) 秒从任何洞口离开,并同样需要 \(1\) 秒从任何洞口进入洞穴。不同的洞口有不同的位置,对一个洞口来说,它与冰箱的距离以 \(a_i\) 表示,同样的,一只蚂蚁从冰箱偷出食物再到第 \(i\) 个洞口的时间也是 \(a_i\) 秒。由于蚂蚁们技术高超,从冰箱拿出食物不会消耗它们任何时间。

每只蚂蚁都必须且只能从冰箱偷一次食物。蚂蚁可以任意选择一个洞口出发并进入任何一个洞口,两个洞口可以不同。一个洞口在 \(1\) 秒内只能有一只蚂蚁进出。因为这个原因,有些蚂蚁在偷完食物后需要等待一段时间才能进入洞口。

所以,你作为庞教授的好朋友, 需要计算出蚂蚁们偷出食物的最短时间。时间的定义为至少存在一只蚂蚁在洞穴外的时间总长,正在进出洞口的蚂蚁不被看作在洞穴里。

\(\sum n\leq 5\times 10^5,m\leq 10^{14}\)

Solution

二分答案 \(T\)

首先对于洞口 \(i\),第 \(k\) 个出洞的蚂蚁到冰箱的时间是 \(a_i+k\),第 \(k\) 个入洞的蚂蚁从冰箱出发的时间是 \(a_i+T-k+1\)。两只蚂蚁能够匹配当且仅当第一只蚂蚁到冰箱的时间不超过第二只蚂蚁从冰箱出发的时间。

一定满足出洞的占用的是一段时间上的前缀,入洞的占用的是一段后缀,否则调整之后显然更优。

考虑把入洞的过程也看成从洞里走向冰箱,问题变为需要对每个洞选择两个前缀,\(\{a_i+1,a_i+2,\ldots,a_i+s_i\}\)\(\{a_i+1,a_i+2,\ldots,a_i+t_i\}\),分别表示入洞的蚂蚁到冰箱的时间以及出洞的蚂蚁到冰箱的时间,需要满足 \(s_i+t_i\leq T\)

现在对所有的前缀和后缀分别合并的结果需要找到一个大小为 \(m\) 的匹配,使得每个匹配的两个数加起来不超过 \(T\)

感性理解一下,前缀和后缀的集合一定尽量相同,否则看起来不太平衡?所以每个洞口一定满足入洞的占用不超过前一半,出洞的占用不超过后一半,这样就基本保证了不交这个限制。


后面的过程还是换回最开始的判定。

对于 \(T\) 是偶数的情况,把所有洞口 \(i\) 的前缀 \([a_i+1,a_i+T/2+1]\) 和后缀 \([T/2,T-a_i-1]\) 分别归并起来,从小到大贪心判断匹配数是否大于等于 \(m\) 即可。

如果 \(T\) 是奇数,则每个洞口在正中间的时间会有一点小细节。注意到类型相同的正中间时刻之间是无法匹配的,考虑把正中间的时刻拆成可以进出各 \(0.5\) 只蚂蚁,根据对称性,拆除来的两个小蚂蚁最终匹配的结果一定(?)也对称,可以看成一只中间的蚂蚁匹配两个对称蚂蚁中的一个。

时间复杂度:\(O(n\log n+n\log m)\)

Code

#include <bits/stdc++.h>

#define int int64_t

const int kMaxN = 1e5 + 5;

int n, m;
int a[kMaxN];

template<class T>
void merge(std::vector<T> &v1, std::vector<T> &v2) {
  std::vector<T> ret;
  for (int i = 0, j = 0; i < v1.size() || j < v2.size();) {
    if (i < v1.size() && (j == v2.size() || v1[i] < v2[j])) ret.emplace_back(v1[i++]);
    else ret.emplace_back(v2[j++]);
  }
  v1.swap(ret);
}

bool check(int t) {
  std::vector<std::tuple<int, int, int>> vec;
  if (t % 2 == 0) {
    std::vector<std::tuple<int, int, int>> vv[4];
    for (int i = 1; i <= n; ++i) vv[0].emplace_back(a[i] + 1, 0, 2);
    for (int i = 1; i <= n; ++i) vv[1].emplace_back(a[i] + t / 2 + 1, 0, -2);
    for (int i = n; i; --i) vv[2].emplace_back(t / 2 - a[i], 1, 2);
    for (int i = n; i; --i) vv[3].emplace_back(t - a[i], 1, -2);
    for (int i = 0; i < 4; ++i) merge(vec, vv[i]);
  } else {
    std::vector<std::tuple<int, int, int>> vv[6];
    for (int i = 1; i <= n; ++i) vv[0].emplace_back(a[i] + 1, 0, 2);
    for (int i = 1; i <= n; ++i) vv[1].emplace_back(a[i] + t / 2 + 1, 0, -1);
    for (int i = 1; i <= n; ++i) vv[2].emplace_back(a[i] + t / 2 + 2, 0, -1);
    for (int i = n; i; --i) vv[3].emplace_back(t / 2 - a[i], 1, 1);
    for (int i = n; i; --i) vv[4].emplace_back(t / 2 - a[i] + 1, 1, 1);
    for (int i = n; i; --i) vv[5].emplace_back(t - a[i], 1, -2);
    for (int i = 0; i < 6; ++i) merge(vec, vv[i]);
  }
  int cnt = 0, las = 0, now[2] = {0};
  for (int i = 0; i + 1 < (int)vec.size(); ++i) {
    if (i + 1 < (int)vec.size() && std::get<0>(vec[i]) == std::get<0>(vec[i + 1])) continue;
    int len = std::get<0>(vec[i + 1]) - std::get<0>(vec[i]);
    for (int j = i; ~j; --j) {
      if (std::get<0>(vec[j]) != std::get<0>(vec[i])) break;
      now[std::get<1>(vec[j])] += std::get<2>(vec[j]);
    }
    if (now[0] >= now[1]) {
      cnt += now[1] * len, las += (now[0] - now[1]) * len;
    } else {
      cnt += now[0] * len;
      int c1 = (now[1] - now[0]) * len;
      cnt += std::min(c1, las), las -= std::min(c1, las);
    }
  }
  return cnt >= 2 * m;
}

void dickdreamer() {
  std::cin >> n >> m;
  for (int i = 1; i <= n; ++i) std::cin >> a[i];
  std::sort(a + 1, a + 1 + n);
  int L = 0, R = 2e14, res = 2e14;
  while (L + 1 < R) {
    int mid = (L + R) >> 1;
    if (check(mid)) R = res = mid;
    else L = mid;
  }
  std::cout << res << '\n';
}

int32_t main() {
#ifdef ORZXKR
  freopen("in.txt", "r", stdin);
  freopen("out.txt", "w", stdout);
#endif
  std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
  int T = 1;
  std::cin >> T;
  while (T--) dickdreamer();
  // std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
  return 0;
}
posted @ 2025-09-24 09:33  下蛋爷  阅读(9)  评论(0)    收藏  举报