题解:洛谷 P2827 [NOIP 2016 提高组] 蚯蚓

洛谷 P2827 [NOIP 2016 提高组] 蚯蚓

题意

给定非负整数序列 \(\{a_n\}\)

每次操作将最大的数 \(x\) 分为 \(\lfloor px \rfloor\)\(x-\lfloor px\rfloor\),并且其它的数 \(+q\)

需要知道 \(m\) 次操作内的所有情况。

\(n\le10^5\)\(m\le7\times10^6\)

解法 A 1.0

我们可以将所有数字放入一个数据结构中,支持:

  • 插入数字。
  • 获得最大数字。

显然可以使用优先队列执行这一操作。


具体地,优先队列存储一个二元组 \((x,y)\),其中 \(x\) 表示这个数放入优先队列中的值,\(y\) 表示放入的时间戳。

当前是第 \(i\) 个操作之前,这个二元组的实际长度即为 \(x+(i-y-1)q\)​。

由于比较时 \(i\) 必然是相等的,那么权值就可以等价为(仅保证相对顺序):\(x-yq\)

时间复杂度

  • 枚举每一次操作,\(O(m)\)
    • 查询优先队列,\(O(n+m)\)

时间复杂度 \(O(m\log(n+m))\),预期得分 \([65,85]\)​。

得分

  • 洛谷 \(75\)
  • UOJ \(75\)
  • LOJ \(80\)

改进

由于优先队列存的是一个数对,所以不好改进。

代码

/**
 * Problem: P2827 [NOIP 2016 提高组] 蚯蚓
 * Author:  OIer_wst
 * Date:    2025-06-26
 */
#include <bits/stdc++.h>
using lint = long long;

int n, m, q, u, v, t;

struct Data {
  lint x; // 放入优先队列时的值
  lint y; // 放入时的时间戳

  Data(lint _x, lint _y) : x(_x), y(_y) {}

  inline lint length(int tim) const {
    return x + (tim - y - 1) * q;
  }

  bool operator<(const Data &e) const {
    return x - y * q < e.x - e.y * q;
  }
};

std::priority_queue<Data> heap;

void input() {
  std::cin >> n >> m >> q >> u >> v >> t;
  for (int i = 0, x; i < n; ++i) {
    std::cin >> x;
    heap.push(Data(x, 0));
  }
}

void process(int tim) { // 处理第 tim 个操作
  lint len = heap.top().length(tim); heap.pop();
  if (tim % t == 0) std::cout << len << " ";
  heap.push(Data(int(1.0L * u / v * len), tim));
  heap.push(Data(len - int(1.0L * u / v * len), tim));
}

int main() {
  std::cin.tie(0)->sync_with_stdio(0);
  input();

  for (int i = 1; i <= m; ++i) process(i);
  std::cout << "\n";

  int rnk = 0;
  while (heap.size()) {
    if ((++rnk) % t == 0)
      std::cout << heap.top().length(m + 1) << " ";
    heap.pop();
  }

  std::cout << std::endl;
  return 0;
}

解法 B 1.0

由于每次都需要给全局 \(+q\),所以可以维护一个 \(\Delta\),表示现在的数 \(+\Delta\) 之后为真实值。

那么当分裂 \(x\) 时,应该放入 \(L(x)=\lfloor p(x+\Delta)\rfloor-\Delta-q\)\(R(x)=x+\Delta-\lfloor p(x+\Delta)\rfloor-\Delta-q\),然后 \(\Delta\gets\Delta+q\)

可以用堆维护这一些数。

时间复杂度

  • 枚举每一次操作,\(O(m)\)
    • 查询堆,\(O(\log(n+m))\)

时间复杂度 \(O(m\log(n+m))\),预测得分 \([65,85]\)​,但是估计常数比解法 A 1.0小。

得分

  • 洛谷 \(85\)
  • UOJ \(85\)
  • LOJ \(85\)

代码

/**
 * Problem: P2827 [NOIP 2016 提高组] 蚯蚓
 * Author:  OIer_wst
 * Date:    2025-06-26
 */
#include <bits/stdc++.h>
using lint = long long;

lint delta;
int n, m, q, u, v, t;

std::priority_queue<lint> heap;

void input() {
  std::cin >> n >> m >> q >> u >> v >> t;
  for (int i = 0, x; i < n; ++i) {
    std::cin >> x;
    heap.push(x);
  }
}

void process(int tim) { // 处理第 tim 个操作
  lint len = heap.top() + delta; heap.pop();
  if (tim % t == 0) std::cout << len << " ";
  int test = int(1.0L * u / v * len);
  heap.push(int(1.0L * u / v * len) - delta - q);
  heap.push(len - int(1.0L * u / v * len) - delta - q);
}

int main() {
  std::cin.tie(0)->sync_with_stdio(0);
  input();

  for (int i = 1; i <= m; ++i) process(i), delta += q;
  std::cout << "\n";

  int rnk = 0;
  while (heap.size()) {
    if ((++rnk) % t == 0)
      std::cout << heap.top() + delta << " ";
    heap.pop();
  }

  std::cout << std::endl;
  return 0;
}

解法 B 2.0

参考 题解:洛谷 P6033 [NOIP 2004 提高组] 合并果子 加强版 里的做法,容易想到可以用多个队列模拟优先队列。

显然可以开三个优先队列 \(q_1,q_2,q_3\),分别存储 \(x,L(x),R(x)\)

猜想是需要证明的。

不妨第 \(k\) 次操作时 \(x_1 \ge x_2\),那么假设先选择 \(x_1\),下一个选择 \(x_2\)

\(x_1\)\(k\) 次变为 \(\lfloor px_1\rfloor\)\(x_1-\lfloor px_1\rfloor\),那么第 \(k+1\) 次变为 \(l_1=\lfloor px_1\rfloor+q\)\(r_1=x_1-\lfloor px_1\rfloor+q\)

\(x_2\) 在第 \(k+1\) 次原数变为 \(x_2+q\),分裂为 \(l_2=\lfloor p(x_2+q) \rfloor\)\(r_2=x_2+q-\lfloor p(x_2+q)\rfloor\)​。


证明 \(l_1\ge l_2\)

\[\begin{aligned} q&\ge pq&p\in(0,1)\\ px_2+q&\ge px_2+pq\\ px_1+q&\ge px_2+pq&x_1\ge x_2\\ \lfloor px_1+q\rfloor&\ge \lfloor px_2+pq \rfloor&\text{高斯函数为{\color{red}单调不降}函数}\\ \lfloor px_1\rfloor+q&\ge\lfloor p(x_2+q) \rfloor& q\in\mathbb{N}\\ l_1&\ge l_2\\ \end{aligned} \]

证毕。


证明 \(r_1\ge r_2\)​。

引理:\(x-\lfloor y \rfloor=\lceil x-y\rceil(x\in\mathbb{Z})\)

\[\begin{aligned} x_1&\ge x_2\\ (1-p)x_1&\ge (1-p)x_2&p<1\\ (1-p)x_1+q&\ge (1-p)x_2+q-pq&pq>0\\ x_1-px_1+q&\ge x_2+q-px_2-pq\\ \lceil x_1-px_1+q \rceil & \ge \lceil x_2+q-px_2-pq \rceil\\ x_1-\lfloor px_1\rfloor+q& \ge x_2+q-\lfloor p(x_2+q)\rfloor&\text{引理}\\ r_1&\ge r_2 \end{aligned} \]

证毕。

由于这些数字是单调不增的,所以直接插入末尾即可。

时间复杂度

  • 枚举每一次操作,\(O(m)\)
    • 操作队列,\(O(1)\)

时间复杂度 \(O(m)\),预期得分 \(100\)

代码

/**
 * Problem: P2827 [NOIP 2016 提高组] 蚯蚓
 * Author:  OIer_wst
 * Date:    2025-06-26
 */
#include <bits/stdc++.h>
using lint = long long;
const int N = 1e5 + 10, M = 7e6 + 10;

lint delta;
int n, m, Q, u, v, t;

lint q[3][M], hh[3], tt[3];

void input() {
  std::cin >> n >> m >> Q >> u >> v >> t;
  hh[0] = 1, tt[0] = n, hh[1] = hh[2] = 0, tt[1] = tt[2] = -1;
  for (int i = 1; i <= n; ++i) std::cin >> q[0][i];
  std::sort(q[0] + 1, q[0] + n + 1, std::greater<int>());
}

int get_max() { // 找到队头最大的队列
  lint x = LLONG_MIN, id = -1;
  if (hh[0] <= tt[0] && q[0][hh[0]] > x) x = q[0][hh[0]], id = 0;
  if (hh[1] <= tt[1] && q[1][hh[1]] > x) x = q[1][hh[1]], id = 1;
  if (hh[2] <= tt[2] && q[2][hh[2]] > x) x = q[2][hh[2]], id = 2;
  return id;
}

void process(int tim) { // 处理第 tim 个操作
  int id = get_max();
  lint len = q[id][hh[id]] + delta; ++hh[id];
  if (tim % t == 0) std::cout << len << " ";
  q[1][++tt[1]] = lint(1.0L * u / v * len) - delta - Q;
  q[2][++tt[2]] = len - lint(1.0L * u / v * len) - delta - Q;
}

int main() {
  std::cin.tie(0)->sync_with_stdio(0);
  input();

  for (int i = 1; i <= m; ++i) process(i), delta += Q;
  std::cout << "\n";

  int rnk = 0;
  while (true) { // 存在非空队列
    int id = get_max();
    if (id == -1) break;
    if ((++rnk) % t == 0) std::cout << q[id][hh[id]] + delta << " ";
    ++hh[id];
  }

  std::cout << std::endl;
  return 0;
}
posted @ 2025-06-28 12:56  OIer_wst  阅读(19)  评论(0)    收藏  举报