[lnsyoj2209/luoguP10455]Genius Acm

来源

原题链接
2024.7.25 校内测验 T2

题意

给定序列 \(P\),将 \(P\) 分为连续的 \(l\) 段,使得对于任意的长度为 \(x\) 的一段,其中任意 \(min\{m, \lfloor \frac{x}{2} \rfloor\}\) 对数的差的平方和不超过 \(k\),求最小的 \(l\)

赛时 0PTS

sol

首先,对于 \(4\) 个数 \(a<b<c<d\),可以证明 \((d-a)^2 + (c-b)^2 \ge (d-b)^2 + (c-a)^2\)

Prove

\[d^2 - 2 ad + a ^ 2 + c^2 - 2bc + b^2 \ge d^2 - 2bd + b^2 + c^2 - 2ac + a^2 \]

\[-2ad - 2bc + 2bd + 2ac \ge 0 \]

\[(d-c)(b-a)\ge 0 \]

因此每次取最大和最小数作差平方即可。
由于分段是连续且完全覆盖序列 \(P\) 的,因此前一个区间右端点 \(r_{i-1}\) 确认之后,当前区间的左端点即已确定为 \(r_{i-1}+1\),我们可以根据贪心,计算出一个最右的右端点,可以证明这样计算出的答案一定为最优解。
由于 \(n,m\) 的数据范围达到了 \(5\times 10^5\) ,因此我们每次计算的复杂度必须为 \(O(n^2)\) 以下。
为了降低复杂度,我们考虑倍增:
每次从 \(1\) 开始向上加,并进行一次判断,判断时如果这一段满足要求,下一次加就加倍,否则下一次加就减半
判断时,可以对序列进行归并排序,使得取第 \(k\) 大值的时间复杂度达到 \(O(1)\),若满足要求,将这一段复制到原序列即可。

代码

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;
typedef long long LL;

const int N = 500005;

int T;
int n, m;
LL k;
LL v[N];
LL tmp[N], temp[N];

bool check(int l, int r, int len){
    int mid = r - len + 1;
    for (int i = l; i <= r; i ++ ) tmp[i] = v[i];
    sort(tmp + mid, tmp + r + 1);
    int i, j, idx;
    for (i = l, j = mid, idx = 0; i < mid && j <= r; ){
        while (i < mid && tmp[i] <= tmp[j]) temp[ ++ idx] = tmp[i ++ ];
        while (j <= r && tmp[j] <= tmp[i]) temp[ ++ idx] = tmp[j ++ ];
    }
    while (i < mid) temp[ ++ idx] = tmp[i ++ ];
    while (j <= r) temp[ ++ idx] = tmp[j ++ ];
    int mm = min(idx / 2, m);
    LL res = 0;
    for (int i = 1, j = idx; i <= mm; i ++ , j -- ) res += (temp[j] - temp[i]) * (temp[j] - temp[i]);
    return res <= k;
}

int find(int x){
    int l = x - 1, p = 1;
    while (p){
        if (l + p <= n && check(x, l + p, p)){
            for (int i = x; i <= l + p; i ++ ) v[i] = temp[i - x + 1];
            l += p;
            p <<= 1;
        }
        else p >>= 1;
    }
    return l;
}

int main(){
    scanf("%d", &T);
    while (T -- ){
        scanf("%d%d%lld", &n, &m, &k);
        for (int i = 1; i <= n; i ++ ) scanf("%lld", &v[i]);
        int pos = 1, ans = 0;
        while (pos <= n) pos = find(pos) + 1, ans ++ ;
        printf("%d\n", ans);
    }
    return 0;
}
posted @ 2024-07-25 14:57  是一只小蒟蒻呀  阅读(8)  评论(0编辑  收藏  举报