题解:洛谷 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;
}