题目
Genius ACM
题解
我们这题使用倍增+归并的思想,要使平方差最大当然是离越远越好,那么自然而然会想到排序。
-
检查合法性函数
check(l, r)
:- 提取子数组 ( a[l:r] ) 并拷贝到数组
b
进行排序。 - 计算前 ( m ) 对最大平方差和。
- 如果总和小于等于 ( k ),返回
true
,表示该子段可行。
- 提取子数组 ( a[l:r] ) 并拷贝到数组
-
倍增搜索找最优分割点:
- 采用倍增(指数增长)查找当前段能扩展到的最大右边界:
- 设
p = 1
(步长),r = l
(当前右边界)。 - 若
check(l, r + p)
可行,则扩展r += p
,并倍增p *= 2
。 - 否则
p /= 2
,进行细粒度搜索。
- 设
- 找到最大合法段后,计数
cnt++
,更新l = r
,继续下一段查找。
- 采用倍增(指数增长)查找当前段能扩展到的最大右边界:
-
复杂度分析
check(l, r)
: 需要对b
排序,复杂度为 ( O(nlog n) )。- 每次
while(l < n)
:- 指数倍增查找 ( O(log n) ) 次
check(l, r)
。 - 整体最坏情况 ( O(n(logn)^2) )。
- 指数倍增查找 ( O(log n) ) 次
我们提交答案发现代码运行居然超时了,那我们该怎么进行优化呢。我们会发现对于排序片段中,有部分是上次就排序好了,我们不需要将他们再进行一次排序,只需要把后来加入的排序,最后归并一下就好了。此时优化后的代码时间复杂度就来到了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;
}