AcWing 天才ACM
Description
- 给定一个整数 ,对于任意一个整数集合 S从集合 S 中取出 对数(即 2∗M 中的整数不够 M 的“校验值”。现在给定一个长度为 N 的数列 以及一个整数 T我们要把 A 分成若干段,使得每一段的“校验值”都不超过 。求最少需要分成几段。
- 第一行输入整数 ,代表有 K对于每组测试数据,第一行包含三个整数 N,M,T 。第二行包含 N 个整数,表示数列。
Output
Data Size
- 1≤K≤12,
1≤N,M≤500000,
0≤T≤1018,
0≤A_i≤220
2
5 1 49
8 2 1 7 9
5 1 64
8 2 1 7 9
Sample Output
2
1
题解:
- 倍增 + 归并排序
- 首先要让每对数的差的平方之和最大,不就是最大跟最小匹配,次大跟次小匹配吗?然后贪心的考虑,如果我分的段越多,那么每一段就越不容易超过T。那么题目要我求最少分多少段,也就是问每一段都要在<=T的前提下包含尽量多的数。
- 所以每一段到底要包含多少个数呢?可以二分。
- 二分然后对所求出的区间sort,找出“校验值”。<=T返回true,否则返回false
- 但是可以告诉你,这样会被卡。
- 因为sort的复杂度是O(nlogn),然后二分如果每次每一段的右端点都只向右边挪动了一点点,复杂度连暴力都不优… …这时候就拼RP了
- 所以,要换一种更保险的方法
- 可以用倍增。理论基础是任意数都可以被拆成多个2的x次方相加。倍增流程如下:
- 初始化r = l, p = 1
- 求出[l, r + p]这一段区间的“校验值”,若校验值<=T,则r += p, p *= 2,否则p /= 2
- 重复上一步,直到p值变为0,此时r为所求右边界
- 倍增的好处主要是区间是从小到大逐渐变大的,这样旧的区间就不用反复的sort,而是只用sort后面新加进来的一小段区间,然后用归并排序的思想合并成一个大区间即可。
- 复杂度大概为O(nlogn)
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#define N 500005
#define int long long
using namespace std;
int T, n, m, k;
int a[N], b[N], c[N], t[N];
int read()
{
int x = 0; char c = getchar();
while(c < '0' || c > '9') c = getchar();
while(c >= '0' && c <= '9') {x = x * 10 + c - '0'; c = getchar();}
return x;
}
bool merge(int l1, int r1, int l2, int r2)
{
int cnt = 0, p1 = l1, p2 = l2, r = 0;
while(p1 <= r1 && p2 <= r2)
{
if(c[p1] <= b[p2]) t[++cnt] = c[p1], p1++;
else t[++cnt] = b[p2], p2++;
}
while(p1 <= r1) t[++cnt] = c[p1], p1++;
while(p2 <= r2) t[++cnt] = b[p2], p2++;
p1 = 1, p2 = cnt, cnt = 0;
while(cnt < m && p1 < p2)
{
r += (t[p1] - t[p2]) * (t[p1] - t[p2]);
p1++, p2--, cnt++;
}
if(r <= k)
{
cnt = 0;
for(int i = l1; i <= r2; i++) c[i] = t[++cnt];
return 1;
}
else return 0;
}
bool check(int l, int r1, int r2)
{
for(int i = r1 + 1; i <= r2; i++) b[i] = a[i];
sort(b + r1 + 1, b + 1 + r2);
return merge(l, r1, r1 + 1, r2);
}
signed main() //因为宏定义用了骚皮操作,所以这里只能写signed
{
cin >> T;
while(T--)
{
n = read(), m = read(), k = read();
for(int i = 1; i <= n; i++) a[i] = read();
int ans = 0,l = 1, r, p;
while(1)
{
c[l] = a[l], r = l, p = 1, ans++;
while(p)
{
if(check(l, r, min(n, r + p))) r += p, p *= 2;
else p /= 2;
if(min(n, r) == n) break;
}
l = r + 1;
if(min(n, r) == n) break;
}
printf("%lld\n", ans);
}
return 0;
}