视频加载失败

安吉D16 T4

1338 字
7 分钟
安吉D16 T4
原题呈现

01 的冒泡排序#

题目描述#

01 学习了冒泡排序,觉得太简单,于是创造了属于自己的排序方式.

给定一个长度为 nn 的排列 aa 以及一个神秘数字 kk,01 会执行以下操作:

  1. 初始令 cnt=0cnt = 0
  2. 如果当前数组已经排好序(即非降序),则停止操作.
  3. 否则,按 ii11nk+1n - k + 1 遍历:
    • 若子区间 ai,ai+1,,ai+k1a_i, a_{i+1}, \ldots, a_{i+k-1} 并非升序,则将该区间从小到大排序,同时令 cnt=cnt+1cnt = cnt + 1
  4. 一轮结束,回到第二步.(这一步时题解作者增加的,或许加上会更严谨一些?)

你需要求出操作结束时 cntcnt 的值.

输入格式#

第一行输入两个整数 n,kn, k,表示数组长度和神秘数字. 第二行输入 nn 个整数 a1,a2,,ana_1, a_2, \ldots, a_n,表示给定的排列.

输出格式#

输出一行一个整数,表示最终的 cntcnt 值.

样例#

输入样例 1#

5 2
1 4 5 3 2

输出样例 1#

5

数据范围#

  • 对于 20%20\% 的数据,保证 n2000n \le 2000
  • 对于另 10%10\% 的数据,保证 k=2k = 2
  • 对于另 20%20\% 的数据,保证 1ik, ai=i\forall 1 \le i \le k,\ a_i = i
  • 对于 100%100\% 的数据:
    • 1n2×1051 \le n \le 2 \times 10^5
    • 2kn2 \le k \le n

我们回忆冒泡排序的过程,实际上,冒泡排序每一次可以让未排序的最大的一个数归位,那么变种有没有这种性质呢?

我们尝试模拟这个流程,首先,对区间 [1,k][1,k] 排序,然后,丢下了最小的一个数,区间右移到 [2,k+1][2, k+1].显然,此时区间内,[2,k][2,k] 里面存放的是 [1,k][1,k] 中前 k1k-1 大的 k1k-1 个数.将其扩展到 ii当遍历到区间 [i,i+k1][i, i+k-1] 时,其中的 [i,i+k2][i,i+k-2][1,i+k2][1, i+k-2] 中最大的 k1k-1 个数

我们来转向结束条件.题目中的结束条件是:

如果当前数组已经排好序(即非降序),则停止操作.

显然,我们不能对于每一个数,都将它和前一个数比较,这样不好维护.我们知道,一个数组是有序的当且仅当每个数前面都没有比它大的数,因此,我们定义 bib_i 表示位置 ii 之前比 aia_i 大的数.这样,结束条件就从“有序”转化为了 i,bi=0\forall i,b_i=0bib_i 可以使用树状数组快速算出.

那要如何才能使得 bib_i 减小呢?我们在每一次区间右移时,区间左边的 k1k-1 个数都是有序的,只有最后一个数是无序的.因此,这就相当于将最后一个数插入到前面这个序列中.此时 bib_i 变化如下:

  • bi>k1b_i > k-1,由于当前区间只有 kk 个数,且前 k1k-1 个数是当前已遍历到的最大的 k1k-1 个数.又因为比 ii 大的至少也有 kk 个.因此,这表示 ii 是当前区间内最小的数,将 ii 的位置前移 k1k-1,即 bibik+1b_i\to b_i-k+1
  • bik1b_i \leq k-1,由于比 bib_i 小的数最多也只有 k1k-1 个,那么这 bib_i 个全在当前区间里了.这说明将当前区间进行一次排序就足以把所有比 aia_i 大的数移动到 aia_i 后面了,此时,aia_i 前面已经没有比 aia_i 小的数了,故 bi0b_i\to 0

综上所述,ii 需要移动 bik1\lceil\frac{b_i}{k-1}\rceil 次才能移动到目标位置.当然,所有最后一次移动到区间 [1,k][1,k] 的数可以一次性全部排序使其回到正确的位置.故若设 t1=bik1t_1=\lceil\frac{b_i}{k-1}\rceilt2t_2 表示当前点每一次移动 k1k-1 移动到区间 [1,k][1,k] 需要移动的次数:

  • t1t2t_1 \leq t_2,则表示 ii 根本不会移动到 [1,k][1,k],或是移动到时 bib_i 已经为 0,不需要集体排序.
  • t1>t2t_1>t_2,则表示 ii 在进行了 t2t_2 次移动后,移动到了 [1,k][1,k],由于每一轮每一个数字最多只能移动 k1k-1 步一次,故此时已经过去 t2t_2 轮.会在第 t2+1t_2+1 轮预定一次 [1,k][1,k] 的集体排序.

特别的,一开始就在 [1,k][1,k] 的数若 bi0b_i\neq 0,则会在第一轮 [1,k][1, k] 的集体排序中恢复有序.

对于每一项计算移动次数加和即可.

标程

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e5 + 100;
int n, k;
int a[N];
bool sfa[N];
// sfa => Sort in the First Action in i th round,是否需要在第 i 轮在区间 [1,k] 进行排序
struct BIT {
int f[N];
void Modify(int pos, int v) {
for (int i = pos; i <= n; i += (i & (-i))) {
f[i] += v;
}
}
int query(int pos) {
if (pos == 0)
return 0;
int ans = 0;
for (int i = pos; i >= 1; i -= (i & (-i))) {
ans += f[i];
}
return ans;
}
int query(int l, int r) { return query(r) - query(l - 1); }
} bit;
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> n >> k;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
long long cnt = 0;
for (int i = 1; i <= k; i++) {
int b = bit.query(a[i], n);
if (b != 0) {
sfa[1] = true;
}
bit.Modify(a[i], 1);
}
for (int i = k + 1; i <= n; i++) {
int b = bit.query(a[i], n);
bit.Modify(a[i], 1);
int t1 = ceil(1.0 * b / (k - 1.0));
int t2 = ceil((i - k + 0.0) / (k - 1.0));
if (t1 <= t2) {
cnt += t1;
} else {
cnt += t2;
sfa[t2 + 1] = true;
}
}
for (int i = 1; i <= n; i++)
cnt += sfa[i];
cout << cnt;
return 0;
}

文章分享

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

安吉D16 T4
https://blog.jerrylab.top/posts/problem/anji2026/D16/T4/
作者
Jerry
发布于
2026-08-17
许可协议
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