題目連結
題意
給你三個整數 n , m , k n,m,k n,m,k 表示一個 n n n 個元素的集合 a i a_i ai,分隔成最連續的若幹段,求小段數,每段的校驗值不超過k。
校驗值計算公式,該段中選出若幹對元素,每對元素(a,b)貢獻為 ( b − a ) 2 (b-a)^2 (b−a)2,校驗值為該段最大貢獻和。
思路
設四個數 a < b < c < d a < b < c < d a<b<c<d
( d − a ) 2 + ( c − b ) 2 − ( ( d − b ) 2 + ( c − a ) 2 ) = … … > 0 (d-a)^2+(c-b)^2 -((d-b)^2+(c-a)^2) = ……>0 (d−a)2+(c−b)2−((d−b)2+(c−a)2)=……>0 是以選兩邊更優
問題可以變為,确定一個左端點,右端點能取多長。擴充右端點,可以倍增擴充。
- 左端點L固定,初始化R=L,P = 1,P即擴充長度。
- 求 [ L , R + P ] [L,R+P] [L,R+P]的校驗值, < = k , 則 R + = P , P ∗ = 2 , 否 則 P / = 2 <=k,則R+=P,P*=2,否則 P/=2 <=k,則R+=P,P∗=2,否則P/=2
- 重複上一步直到P為0,R即所求右端點,L更新為R+1。
一個小優化類似歸并,求校驗值時 [ L , R ] [L,R] [L,R]已經必然成立,使 [ L , R ] [L,R] [L,R]區間為升序,減少了每次 P / = 2 P/=2 P/=2時排序,那麼隻需排序 [ R + 1 , R + P ] [R+1,R+P] [R+1,R+P],然後歸并一下 [ L , R + P ] [L,R+P] [L,R+P],可以使用merge函數減少代碼量,第一次知道merge。
代碼
#include <bits/stdc++.h>
using namespace std;
#define ll long long
ll Rd(ll &_a){return scanf("%lld",&_a);}
ll rd(){ll _tmp; scanf("%lld",&_tmp); return _tmp;}
ll n, m, k;
ll v[500005], tmp[500005], fq[500005];
void sor(ll l, ll ind, ll r)
{
for(ll i = ind+1; i <= r; ++i) tmp[i] = v[i];
sort(tmp+ind+1,tmp+r+1);
merge(v+l,v+ind+1,tmp+ind+1,tmp+r+1,fq);
for(int i = 0; l+i <= r; ++i) tmp[l+i] = fq[i];
}
ll check(ll l, ll ind, ll r)
{
ll sum = 0;
sor(l,ind,r);
for(ll i = min(m,(r-l+1)/2); i; --i)
{
sum += (tmp[l+i-1]-tmp[r-i+1])*(tmp[l+i-1]-tmp[r-i+1]);
if(sum > k) return 0;
}
for(ll i = l; i <= r; ++i) v[i] = tmp[i];
return 1;
}
int main()
{
for(ll t = rd(); t; --t)
{
scanf("%lld%lld%lld",&n,&m,&k);
for(ll i = 1; i <= n; ++i) Rd(v[i]);
ll l = 1, r, p, ans = 0;
while(l <= n)
{
++ans, p = 1, r = l; // 固定左端點l,r為已經确認滿足的最大範圍,p+r為試探範圍
while(p)
{
if(r+p <= n && check(l,r,r+p)) r += p, p <<= 1;
else p >>= 1;
}
l = r+1;
}
printf("%lld\n",ans);
}
return 0;
}