链接: https://cn.vjudge.net/contest/250027#problem/B
题意: 题意就很恶心。 现在给你一个长度为n的序列,将原序列分成尽量小的块(不改变顺序)使得满足下列情况: 要求在每一块中任意取得m对数,(2*m个),不够m对,尽量多取,并且每个数只能用一次。使得满足每一对的差值的平方的和<=K.问你最小块数是多少?
思路参考:坤神博客:https://blog.youkuaiyun.com/nka_kun/article/details/82632471
思路: 最坏的情况 一定是这个块里的(最大和最小的差的平方)+(次大和次小的平方)........。
那么这样我可以枚举左端点,那我要做的就是怎么确定右端点,很容易想 到的就是二分,然后judge,但是肯定是不行的,因二分的复杂度是n*n*logn,因为每次judge就是一个o(n) 。这里我可以从左端点开始倍增,不断的去扩大右端点,这样的话,我每次的judge函数中的判断就肯定会比二分小很多很多。对于起始长度为len=1 ,如果r+len满足条件那么r更新为r+len,并且len*=2,如果不满足条件,那么将len缩小直至满足条件,如果len==0||r>=n 就跳出。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N =5e5+5;
ll a[N],b[N],c[N],d[N];
int n,m;
ll K;
int jud(int len1,int len2)
{
int p1,p2;
p1=1; p2=1;
int tot=0;
while(p1<=len1&&p2<=len2){
if(b[p1]<=c[p2]){
d[++tot]=b[p1];
p1++;
}
else{
d[++tot]=c[p2];
p2++;
}
}
while(p1<=len1){
d[++tot]=b[p1++];
}
while(p2<=len2){
d[++tot]=c[p2++];
}
int pl,pr;
ll sum=0;
pl=1; pr=tot;
int cnt=0;
while(pl<pr&&cnt<m){
cnt++;
sum+=(d[pr]-d[pl])*(d[pr]-d[pl]);
if(sum>K) return 0;
pl++; pr--;
}
if(sum>K) return 0;
for(int i=1;i<=tot;i++) b[i]=d[i];
return 1;
}
int solve(int id)
{
int l,r,len;
l=id; r=id; len=1;
b[1]=a[id];
int tot;
while(1)
{
if(r+len>n) len=n-r;
tot=0;
for(int i=r+1;i<=r+len;i++){
c[++tot]=a[i];
}
sort(c+1,c+tot+1);
if(jud(r-l+1,len)){
r+=len; len*=2;
}
else{
len/=2;
}
if(len==0) break;
if(r>=n) break;
}
return r;
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
scanf("%d %d %lld",&n,&m,&K);
for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
int ans=0;
for(int i=1;i<=n;)
{
i=solve(i)+1;
ans++;
}
printf("%d\n",ans);
}
return 0;
}