XJOI 7191 Genius ACM
二分+倍增
题目
题目中的最大校验值应由数组排序后,取出最大值和最小值,次大值和次小值……进行做差平方取和
所以在加入一个新的数时,校验值是不会下降的
那么可以发现,校验值是单调递增的,所以可以用二分对每一个固定的左段点找到满足条件的最大的右端点
所以l初始值设为1,不断对r进行二分,找到最大的点
进行二分时要用二进制数(倍增),不能直接取mid
设一个偏量p,右端点即为r+p,一开始设为1,然后对其判断,在k的范围内p就乘2,否则p除以2
进行判断时,将整个区间进行排序,取前m个数和后m个数分别做差,算出值与k比较
但此时时间复杂度为O(n*logn*logn),对于n=5*1e5是过不了的
而排序用的时间最多,所以要利用之前排好的元素进行归并排序
#include <bits/stdc++.h> #define ll long long using namespace std; const ll MAXN=5*1e5+100; ll t,n,m,k,a[MAXN],b[MAXN],p; ll ans,c[MAXN]; bool check(ll l,ll r) { ll tot=0; for (ll i=0;i<m;i++) { if (l+i>r-i) break; tot+=(c[l+i]-c[r-i])*(c[l+i]-c[r-i]); } return tot<=k; } void merge(ll la,ll ra,ll lb,ll rb)//合并两个有序数组 { ll l,r,now; l=la; r=lb; now=la-1; while (l<=ra && r<=rb) { if (b[l]<=b[r]) { now++; c[now]=b[l]; l++; } else { now++; c[now]=b[r]; r++; } } for (ll i=l;i<=ra;i++) { now++; c[now]=b[i]; } for (ll i=r;i<=rb;i++) { now++; c[now]=b[i]; } } int main() { scanf("%lld",&t); while (t--) { scanf("%lld%lld%lld",&n,&m,&k); for (ll i=1;i<=n;i++) scanf("%lld",&a[i]); ll l,r; l=1; ans=0; while (l<=n) { ans++;//统计答案 p=1; r=l; b[l]=a[l]; while (p) { if (r+p>n) { p>>=1; continue; } for (int i=r+1;i<=r+p;i++)//r之前的元素已经排好了序,所以对[r+1,r+p]的元素排序 b[i]=a[i]; sort(b+r+1,b+r+p+1); merge(l,r,r+1,r+p);//将两个区间的元素合并 if (check(l,r+p)) { for (int i=l;i<=r+p;i++)//注意,这句不能放在merge函数中,因为p有可能变小,之前排好序的元素可能排到了r之后,在之后统计答案时无法统计到 b[i]=c[i]; r+=p; p<<=1; } else { p>>=1; } } l=r+1; } printf("%lld\n",ans); } }