每日一题——Genius ACM

题目

Genius ACM

题解

我们这题使用倍增+归并的思想,要使平方差最大当然是离越远越好,那么自然而然会想到排序。

  1. 检查合法性函数 check(l, r)

    • 提取子数组 ( a[l:r] ) 并拷贝到数组 b 进行排序。
    • 计算前 ( m ) 对最大平方差和。
    • 如果总和小于等于 ( k ),返回 true,表示该子段可行。
  2. 倍增搜索找最优分割点

    • 采用倍增(指数增长)查找当前段能扩展到的最大右边界
      • p = 1(步长),r = l(当前右边界)。
      • check(l, r + p) 可行,则扩展 r += p,并倍增 p *= 2
      • 否则 p /= 2,进行细粒度搜索。
    • 找到最大合法段后,计数 cnt++,更新 l = r,继续下一段查找。
  3. 复杂度分析

  • check(l, r): 需要对 b 排序,复杂度为 ( O(nlog n) )。
  • 每次 while(l < n)
    • 指数倍增查找 ( O(log n) ) 次 check(l, r)
    • 整体最坏情况 ( O(n(logn)^2) )。

我们提交答案发现代码运行居然超时了,那我们该怎么进行优化呢。我们会发现对于排序片段中,有部分是上次就排序好了,我们不需要将他们再进行一次排序,只需要把后来加入的排序,最后归并一下就好了。此时优化后的代码时间复杂度就来到了O(nlogn)。

参考代码(优化前)

#include<iostream>
#include<algorithm>
using namespace std;
#define int long long
const int N = 5e5 + 10;
int a[N],b[N];
int n, m, T;
bool check(int l, int r){
    int k = 0;
    for(int i = l; i < r; i ++) b[k ++] = a[i];
    sort(b, b + k);
    int sum = 0;
    for(int i = 0; i < m && i < k; i ++ , k --) sum += (b[k - 1] - b[i]) * (b[k - 1] - b[i]);
    return sum <= T;
}
void solve(){
    cin >> n >> m >> T;
    for(int i = 0; i < n; i ++) cin >> a[i];
    int l = 0, cnt = 0;
    while(l < n){
        int p = 1, r = l;
        while(p){
            if(r + p <= n && check(l, r + p)){
                r += p;
                p *= 2;
            }else{
                p /= 2;
            }
        }
        cnt ++;
        l = r;
    }
    cout << cnt << endl;
}
signed main(){
    int _ = 1;
    cin >> _;
    while(_ --){
        solve();
    }
    return 0;
}

参考代码(优化后)

#include<iostream>
#include<algorithm>
using namespace std;
#define int long long
const int N = 5e5 + 10;
int a[N],b[N],t[N];
int n, m, T;
bool check(int l, int mid, int r){
    for(int i = mid; i < r; i ++) b[i] = a[i];
    sort(b + mid, b + r);
    int i = l, j = mid, k = 0;
    while(i < mid && j < r){
        if(b[i] < b[j]) t[k ++] = b[i ++];
        else t[k ++] = b[j ++];
    }
    while(i < mid) t[k ++] = b[i ++];
    while(j < r) t[k ++] = b[j ++];
    int sum = 0;
    for(int i = 0; i < m && i < k; i ++ , k --) sum += (t[k - 1] - t[i]) * (t[k - 1] - t[i]);
    return sum <= T;
}
void solve(){
    cin >> n >> m >> T;
    for(int i = 0; i < n; i ++) cin >> a[i];
    int l = 0, cnt = 0;
    while(l < n){
        int p = 1, r = l;
        while(p){
            if(r + p <= n && check(l, r, r + p)){
                r += p;
                p *= 2;
                for(int i = l; i < r; i ++) b[i] = t[i - l];
            }else{
                p /= 2;
            }
        }
        cnt ++;
        l = r;
    }
    cout << cnt << endl;
}
signed main(){
    int _ = 1;
    cin >> _;
    while(_ --){
        solve();
    }
    return 0;
}
posted @ 2025-03-20 21:33  PZnwbh  阅读(20)  评论(0)    收藏  举报