我们发现,二分之所以慢,是因为它的枚举方向,所以可以用倍增替代掉二分。我们发现,可以将二分右端点的部分改成:先从小往大尝试将后面二的幂个元素加入备选序列,直到原序列中元素不足这么多个或者加入备选序列后的 SPD 值大于 k。然后再从最后的加入备选序列的数量开始从大往小尝试将后面二的幂个元素加入备选序列,每次尝试时,如果加入后的备选序列的 SPD 值不大于 k,则加入备选序列;否则,即加入后的备选序列的 SPD 值大于 k,则不加入备选序列。其它的就跟二分的过程差不多了。
时间复杂度:外层一样的 \Omicron(n),内层每次倍增最多排序 2 \log_2 n 次(常数忽略不计)。由 (1),时间复杂度为 \Omicron(n^2 \log_2 n),与二分一样。
四、倍增 + 归并
在上面倍增的思路中,我们发现每次倍增中进行的排序的序列左端点都是重合的,只是右边延伸了一段。所以可以想到,将原来直接排序的部分修改成:先将加入备选序列的部分排好序(当然不能存在原数组中),然后用归并(双指针)的方式将两个有序序列合并成一个大的有序序列。
时间复杂度:外层还是 \Omicron(n),内层每次倍增最多归并 2 \log_2 n 次(常数忽略不计)。那每次倍增的复杂度是 \Omicron(n \log_2 n),总的时间复杂度是否还是 \Omicron(n^2 \log_2 n) 呢?不是。我们发现,每一段只会被排一次序。所以,总的排序操作的时间复杂度的和应该不超过 \Omicron(n \log_2 n)。而归并的时间复杂度的和也不超过 \Omicron(n \log_2 n)。所以总的时间复杂度就是 \Omicron(n \log_2 n)。通过!
不过,归并完后的时候可以将结果复制回原数组(如果结果的 SPD 值不大于 k 的话)。
代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
#define getchar()(p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
char buf[1<<21],*p1=buf,*p2=buf;
template <typename T>
inline void read(T& r) {
r=0;bool w=0; char ch=getchar();
while(ch<'0'||ch>'9') w=ch=='-'?1:0,ch=getchar();
while(ch>='0'&&ch<='9') r=r*10+(ch^48), ch=getchar();
r=w?-r:r;
}
int T, n, m, k, ii, ans, p[500010], a[500010], b[500010];
bool check(int l, int r, int rr) {
if(rr > n) return false;
int p1 = l, p2 = r + 1, p3 = l, s = 0;
for(int i = l; i <= rr; i ++) {
a[i] = p[i];
}
sort(a + r + 1, a + rr + 1);
while(p1 <= r && p2 <= rr) {
if(a[p1] < a[p2]) b[p3++] = a[p1++];
else b[p3++] = a[p2++];
}
while(p1 <= r) b[p3++] = a[p1++];
while(p2 <= rr) b[p3++] = a[p2++];
for(int i = l, j = rr, o = 1; i < j && o <= m; i ++, j --, o ++) {
s += ((b[j] - b[i]) * (b[j] - b[i]));
if(s > k) return false;
}
for(int i = l; i <= rr; i ++) {
p[i] = b[i];
}
return true;
}
signed main()
{
read(T);
while(T --) {
read(n);read(m);read(k);
for(int i = 1; i <= n; i ++) {
read(p[i]);
}
ans = 0;
for(int l = 1, r = 0; l <= n; ++ans) {
for(ii = 1; check(l, r, r + ii); ii <<= 1) {
r += ii;
}
ii >>= 1;
for(; ii; ii >>= 1) {
if(check(l, r, r + ii)) {
r += ii;
}
}
l = r + 1;
}
printf("%d\n", ans);
}
return 0;
}