题意
给定一个整数 M M M,对于任意一个整数集合 S S S,定义“校验值”如下:
从集合 S S S中取出 M M M对数(即 2 ∗ M 2∗M 2∗M个数,不能重复使用集合中的数,如果 S S S中的整 数不够 M M M对,则取到不能取为止),使得“每对数的差的平方”之和最大,这个最大值 就称为集合 S S S的“校验值”。
现在给定一个长度为 N N N的数列 A A A以及一个整数 T T T。我们要把 A A A分成若干段,使得 每一段的“校验值”都不超过 T T T。求最少需要分成几段。
思路
显然集合
S
S
S的“校验值”为
S
S
S中最大值和最小值匹配,次大值和次小值匹配
⋯
\cdots
⋯以此类推。
朴素做法是暴力枚举分界点,使得在每段的“校验值”不超过
T
T
T的条件下尽可能长,直接排序计算扩展段的“校验值”,时间复杂度
O
(
n
2
l
o
g
n
)
O(n^2logn)
O(n2logn)
考虑优化上述做法,用倍增扩展分界点,时间复杂度可降为
O
(
n
l
o
g
2
n
)
O(nlog^2n)
O(nlog2n),若扩展的时候不是重新排序,先对新扩展段进行排序,再用类似归并排序的方法合并原段和新扩展段,时间复杂度可降为
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)。
代码
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <cstdio>
#include <cmath>
#include <iostream>
using namespace std;
#define fo(i, x, y) for (int i = x; i <= y; ++i)
#define fd(i, x, y) for (int i = x; i >= y; --i)
typedef long long ll;
const int maxn = 5e5 + 5;
int n, m;
int a[maxn], b[maxn];
ll k;
int getint()
{
char ch;
int res = 0, p;
while (!isdigit(ch = getchar()) && (ch ^ '-'));
p = ch == '-'? ch = getchar(), -1 : 1;
while (isdigit(ch))
res = (res << 3) + (res << 1) + (ch ^ 48), ch = getchar();
return res * p;
}
ll getval(int l, int r)
{
//fo(i, l, r) b[i] = a[i];
//sort(b + l, b + r + 1);
ll res = 0;
for (int pl = l, pr = r, cnt = 0; pl < pr && cnt < m; pl++, pr--, cnt++)
res += (ll)(b[pr] - b[pl]) * (b[pr] - b[pl]);
return res;
}
int main()
{
//freopen("input", "r", stdin);
int kase;
kase = getint();
while (kase--)
{
n = getint(); m = getint(); scanf("%lld", &k);
fo(i, 1, n) a[i] = getint();
int ans = 0;
for (int len = 1, l = 1, r = 1; r <= n;)
{
len = 1;
while (len)
{
if (r + len <= n)
{
fo(i, r + 1, r + len) b[i] = a[i];
sort(b + r + 1, b + r + len + 1);
merge(a + l, a + r + 1, b + r + 1, b + r + len + 1, b + l);
}
if (r + len <= n && getval(l, r + len) <= k)
{
r += len, len <<= 1;
fo(i, l, r) a[i] = b[i];
}
else len >>= 1;
}
l = r = r + 1;
ans++;
}
printf("%d\n", ans);
}
return 0;
}