题目描述
给定一个整数
M
M
M,对于任意一个整数集合
S
S
S,定义“校验值”如下:
从集合
S
S
S 中取出
M
M
M 对数(即
2
×
M
2×M
2×M 个数,不能重复使用集合中的数,如果
S
S
S 中的整数不够
M
M
M 对,则取到不能取为止),使得“每对数的差的平方”之和最大,这个最大值就称为集合
S
S
S 的“校验值”。
现在给定一个长度为
N
N
N 的数列
A
A
A 以及一个整数
T
T
T。
我们要把
A
A
A 分成若干段,使得每一段的“校验值”都不超过
T
T
T。
求最少需要分成几段。
输入格式
第一行输入整数
K
K
K,代表有
K
K
K 组测试数据。
对于每组测试数据,第一行包含三个整数
N
,
M
,
T
N,M,T
N,M,T 。
第二行包含
N
N
N 个整数,表示数列
A
1
,
A
2
…
A
N
A_1,A_2…A_N
A1,A2…AN。
输出格式
对于每组测试数据,输出其答案,每个答案占一行。
数据范围
1
≤
K
≤
12
,
1≤K≤12,
1≤K≤12,
1
≤
N
,
M
≤
500000
,
1≤N,M≤500000,
1≤N,M≤500000,
0
≤
T
≤
1
0
18
,
0≤T≤10^{18},
0≤T≤1018,
0
≤
A
i
≤
2
20
0≤A_i≤2^{20}
0≤Ai≤220
输入样例:
2
5 1 49
8 2 1 7 9
5 1 64
8 2 1 7 9
输出样例:
2
1
题解
这道题首先裸着打是很好写的,只需要暴力排序枚举瞎搞就行了
但很明显这样写不现实
对于这样大的数据范围,为了优化暴力,最直接的办法当然就是二分,时间复杂度为
O
(
n
2
l
o
g
n
)
O(n^2logn)
O(n2logn)(我也不会算)
于是就有了一个比二分更优的写法:倍增!
它的复杂度是
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)
因为要将区间排序,直接sort的话就会炸掉,所以可以利用归并排序,因为已经提前将某些数字排序过了
具体的倍增过程如下:
用倍增找出区间的长度,如果当前区间符合,就把伸长的长度翻倍,如果不符合就折半即可
#include<bits/stdc++.h>
using namespace std;
const int N=500005;
int n,m,ans;
long long T,w[N], t[N],tmp[N];
bool check(int l, int mid, int r)
{
for(int i=mid;i<r;i++) t[i]=w[i];
sort(t+mid,t+r);
int i=l,j=mid,k=0;
while(i!=mid&&j!=r)
if(t[i]<t[j]) tmp[k++]=t[i++];
else tmp[k++]=t[j++];
while(i!=mid) tmp[k++]=t[i++];
while(j!=r) tmp[k++]=t[j++];
long long sum=0;
for(i=0;i<m&&i<k;i++,k--) sum+=(tmp[i]-tmp[k-1])*(tmp[i]-tmp[k-1]);
return sum<=T;
}
int main()
{
int K;
scanf("%d",&K);
while(K--)
{
scanf("%d%d%lld",&n,&m,&T);
for(int i=0;i<n;i++) scanf("%lld",&w[i]);
ans=0;
int len;
int start=0,end=0;
while(end<n)
{
len=1;
while(len)
{
if(end+len<=n&&check(start,end,end+len))
{
end+=len,len<<=1;
if(end>=n) break;
for(int i=start;i<end;i++) t[i]=tmp[i-start];
}
else len>>=1;
}
start=end;
ans++;
}
printf("%d\n",ans);
}
return 0;
}