【倍增】 Genius acm
传送门
题意
给定一个长度为\(n\)的整数序列\(a\),以及一个数\(t\),和一个\(m\),将\(a\)分成子序列,子序列中满足从中取出\(m\)对数(不能重复取,如果不够取到不能取为止),使得每一对数的差的平方和最大,
将这个最大值定义为序列的校验值,使得子序列的校验值小于等于\(t\),问最少能够分成多少个子序列
数据范围
\(\begin{array}{l}1 \leq K \leq 12 \\ 1 \leq N, M \leq 500000 \\ 0 \leq T \leq 10^{18} \\ 0 \leq A_{i} \leq 2^{20}\end{array}\)
题解
对于一段序列来说,取最大的和最小的组成一对,这样计算出来的和是最大的
-
初始化\(p=1,r = l\)
-
求出\([ l , r+p ]\)的校验值
- 如果校验值\(<=t, r=r + p,p=p\times 2\),否则\(p= \frac{p}{2}\)
-
重复上步,直到\(p=0\)
-
以上的过程最多进行\(O(logn)\)次,每次循环都对$ r-l$ 排序,总体为O\((nlog^{2}n)\),每次求校验值的时候不需要快速排序,用类似归并排序的算法只需要对新增部分排序
Code
#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,n) for(int i=a;i<n;i++)
#define per(i,a,n) for(int i=n-1;i>=a;i--)
const int N=5e5+10;
int _;
int n,m;
ll t;
int a[N],b[N],c[N];
int last;
void merge(int l,int mid,int r){
int i = l,j = mid+1;
for(int k=l;k<=r;k++){
if(j > r || (i <= mid && b[i] < b[j]))
c[k] = b[i++];
else c[k] = b[j++];
}
}
long long cal(int l,int r){
if(r > n) r = n;
int cp = min(m,(r-l+1)/2);
for(int i=last+1,i<=r;i++) b[i] = a[i];
sort(b+last+1,b+r+1);
merge(l,last,r);
ll res=0;
rep(i,0,cp)
res += 1ll * (c[r-i]-c[l+i])*1ll*(c[r-i]-c[l+i]);
return res;
}
void solve()
{
cin>>n>>m>>t;
for(int i=1;i<=n;i++) cin>>a[i];
last=1;
int ans=0,l=1,r=1;
b[1]=a[1];
while(l<=n){
int p=1;
while(p){
long long res = cal(l,r+p);
if(res <= t){
last = r = min( r+p , n );
for(int i=l;i<=r;i++) b[i]=c[i]; // 保存有序形式
if(r==n) break;
p*=2;
}
else p/=2;
}
ans++;
l=r+1;
}
cout<<ans<<endl;
}
int main(){
cin>>_;
while(_--)
solve();
}