AcWing:109. 天才ACM(倍增 + 归并排序)
给定一个整数 MM,对于任意一个整数集合 SS,定义“校验值”如下:
从集合 SS 中取出 MM 对数(即 2∗M2∗M 个数,不能重复使用集合中的数,如果 SS 中的整数不够 MM 对,则取到不能取为止),使得“每对数的差的平方”之和最大,这个最大值就称为集合 SS 的“校验值”。
现在给定一个长度为 NN 的数列 AA 以及一个整数 TT。
我们要把 AA 分成若干段,使得每一段的“校验值”都不超过 TT。
求最少需要分成几段。
输入格式
第一行输入整数 KK,代表有 KK 组测试数据。
对于每组测试数据,第一行包含三个整数 N,M,TN,M,T 。
第二行包含 NN 个整数,表示数列A1,A2…ANA1,A2…AN。
输出格式
对于每组测试数据,输出其答案,每个答案占一行。
数据范围
1≤K≤121≤K≤12,
1≤N,M≤5000001≤N,M≤500000,
0≤T≤10180≤T≤1018,
0≤Ai≤2200≤Ai≤220
输入样例:
2
5 1 49
8 2 1 7 9
5 1 64
8 2 1 7 9
输出样例:
2
1
算法:倍增 + 归并
注意:本题不能直接用sort排序,会时间超限,必须用归并来优化排序。
#include <iostream> #include <cstdio> #include <algorithm> using namespace std; typedef long long ll; const int maxn = 5e5+7; ll n, m, k; ll arr[maxn]; ll a[maxn]; ll b[maxn]; void merge(int l, int mid, int r) { int i = l, j = mid; int t = l; while(i < mid || j <= r) { if((i < mid && a[i] <= a[j]) || j > r) { b[t++] = a[i++]; } else { b[t++] = a[j++]; } } } bool check(int l, int mid, int r) { for(int i = mid; i <= r; i++) { a[i] = arr[i]; } sort(a + mid, a + r + 1); //在mid之前的数都是有序的,从mid开始就是copy的arr数组中的值,所以需要变成有序才能归并 merge(l, mid, r); ll sum = 0; for(int i = l, j = r, cnt = 0; cnt < m && i < j; i++, j--, cnt++) { sum += (b[j] - b[i]) * (b[j] - b[i]); } if(sum <= k) { for(int i = l; i <= r; i++) { a[i] = b[i]; } return true; } return false; } int main() { int T; scanf("%d", &T); while(T--) { cin >> n >> m >> k; for(int i = 1; i <= n; i++) { cin >> arr[i]; } int l = 1, r = 1, h = 1; a[l] = arr[l]; int ans = 0; while(r <= n) { if(h == 0) { //当长度不可取的时候,就开始匹配下一段 ans++; r++; l = r; h = 1; a[l] = arr[l]; } else if(r + h <= n && check(l, r + 1, r + h)) { r += h; h *= 2; if(r == n) { break; } } else { h /= 2; } } if(r == n) { ans++; } cout << ans << endl; } return 0; }