视频加载失败

安吉D8-A

1447 字
7 分钟
安吉D8-A
原题呈现

P3509 ZAB-Frog#

题目描述#

在一个特别长且笔直的 Byteotian 小溪的河床上,有 nn 块石头露出水面.它们距离小溪源头的距离分别为 p1<p2<<pnp_1 < p_2 < \cdots < p_n.一只小青蛙正坐在其中一块石头上,准备开始它的跳跃训练.每次青蛙跳跃到距离它所在石头第 kk 近的石头上.具体来说,如果青蛙坐在位置 pip_i 的石头上,那么它将跳到这样的 pjp_j 上,使得:

{pa:papi<pjpi}k and {pa:papipjpi}>k|\{ p_a : |p _ a - p _ i| < |p_j - p_i| \}| \le k \text{ and } |\{ p_a : |p _ a - p _ i| \le |p_j - p_i| \}| > k

如果 pjp_j 不是唯一的,那么青蛙在其中选择距离源头最近的石头.对于每一块石头分别计算,若青蛙从这块石头开始跳跃,经过 mm 次跳跃后最终会停留在哪一块石头上?

输入格式#

标准输入的第一行包含三个整数 nnkkmm1k<n106,1m10181 \le k < n \le 10^6, 1 \le m \le 10^{18}),用空格分隔,分别表示石头的数量、参数 kk 和计划跳跃的次数.第二行包含 nn 个整数 pjp_j1p1<p2<<pn10181 \le p_1 < p_2 < \cdots < p_n \le 10^{18}),用空格分隔,表示小溪河床上连续石头的位置.

输出格式#

你的程序应在标准输出上打印一行,包含 nn 个整数 r1,r2,,rnr_1, r_2, \cdots, r_n,用空格分隔.数字 rir_i 表示从输入顺序中的第 ii 块石头开始跳跃 mm 次后,青蛙最终停留的石头编号.

输入输出样例 #1#

输入 #1#

5 2 4
1 2 4 7 10

输出 #1#

1 1 3 1 1

说明/提示#

样例 #1 解释:#

图中展示了青蛙从每块石头跳跃(单次跳跃)到的位置.

我们发现此题中,mm 的范围是 1e181e18,这显然不可能模拟跳那么多步.

通过模拟样例,我们可以发现:从一个格子开始,跳到的下一个格子是固定的.因此,考虑使用倍增.

具体来说,分别预处理出每一个点跳 1 次、跳 2 次、跳 4 次……,设点 ii 跳了 2j2^j 次之后落在了 f[j][i]f[j][i],则转移方程为:

f[i][j] = f[i-1][f[i-1][j]];

可以枚举 mm 中所有二进制为 1 的位数 xx(如 66 的二进制表示为 110110,由于二进制表示中为 1 的数位是右起第二位和右起第三位,因此此时 x{2,3}x\in\{2, 3\}),初始时,设每一个格子 ii 上都有一只青蛙,这只青蛙实时位置为 aia_i,对于每一个 xx,将每一个格子 aia_i2x12^{x-1} 次,即 a[i] = f[x-1][i]

最终 aia_i 中记录的值就是答案了.

那么,怎么求出最初始的 f[0][i]f[0][i],即每一个点跳一步会落在哪里呢?

使用滑动窗口.维护一个大小始终为 k+1k+1 的滑动窗口 [l,r][l, r].初始时,将前 k+1k+1 个数加入滑动窗口,之后,对于每一个点 ii,维护这个滑动窗口,使得其恰好包含 ii 和离 ii 最近的前 kk 个数:

  • 尝试将此窗口整体右移 1 格.具体的:若滑动窗口外右边第一个元素 r+1r+1ii 的距离比滑动窗口中最左边元素 ll 小,即第 r+1r+1 块石头离 ii 比第 ll 块石头近,此时,将滑动窗口右移一格.重复这个操作,直到上述条件不满足.
  • 接着,比较滑动窗口中最左端的 ll 和最右端的 rr,这两块石头是滑动窗口中离 ii 最远的两块,比较谁离 ii 更远,更远的一块就是 ii 下一步会跳到的石头.

可以写出代码:

#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 1;
typedef long long ll;
const int INF = 0x3f3f3f3f;
ll n, k, m, a[N];
int f[60][N];
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> n >> k >> m;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
a[n + 1] = INF; // 哨兵
int l = 1, r = k + 1;
for (int i = 1; i <= n; i++) {
while (r < n && a[r + 1] - a[i] < a[i] - a[l]) {
l++;
r++;
}
if (a[i] - a[l] >= a[r] - a[i])
f[0][i] = l;
else
f[0][i] = r;
}
for (int i = 1; i <= 59; i++) {
for (int j = 1; j <= n; j++) {
f[i][j] = f[i - 1][f[i - 1][j]];
}
}
for (int i = 1; i <= n; i++) {
ll x = i, st = m, wt = 0;
while (st >= 1) {
if (st % 2) {
x = f[wt][x];
}
st /= 2;
wt++;
}
cout << x << ' ';
}
}

如果这样写,会导致 MLE.具体来说,由于此题的 125MB 内存限制,我们只能存 3×1073\times 10^7 个 int 类型变量.而 f 数组中有 60×106=6×10760\times 10^6 = 6\times 10^7 个 int 变量.

此题应该选用滚动倍增数组进行优化.具体来说,省去 f 数组,在计算 aia_i 时,动态计算当前需要的倍增数组.

标程

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e6 + 100;
typedef long long ll;
const ll INF = 2e18;
int n, k;
ll m, a[N];
int nxt[N], cur[N], tmp[N];
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> n >> k >> m;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
a[n + 1] = INF;
int l = 1, r = k + 1;
for (int i = 1; i <= n; i++) {
while (r < n && a[r + 1] - a[i] < a[i] - a[l]) {
l++;
r++;
}
if (a[i] - a[l] >= a[r] - a[i])
nxt[i] = l;
else
nxt[i] = r;
}
for (int i = 1; i <= n; i++) {
cur[i] = i;
}
while (m >= 1) {
if (m & 1) {
for (int i = 1; i <= n; i++) {
tmp[i] = cur[i];
}
for (int i = 1; i <= n; i++) {
cur[i] = nxt[tmp[i]];
}
}
m /= 2;
for (int i = 1; i <= n; i++)
tmp[i] = nxt[i];
for (int i = 1; i <= n; i++)
nxt[i] = tmp[tmp[i]];
}
for (int i = 1; i <= n; i++) {
cout << cur[i] << ' ';
}
}

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

安吉D8-A
https://blog.jerrylab.top/posts/problem/anji2026/D8/A/
作者
Jerry
发布于
2026-08-09
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
Jerry
Hello, I'm Jerry.
公告
欢迎来到我的博客!这是一则示例公告。
分类
标签
最新动态
站点统计
文章
80
分类
3
标签
35
总字数
89,198
运行时长
0
最后活动
0 天前
站点信息
构建平台
Cloudflare Pages
博客版本
Firefly v6.16.5
文章许可
CC BY-NC-SA 4.0