AcWing 109 天才ACM(倍增)
基本思路
这道题最基本的想法就是一次寻找每个区间,对于每个区间,用二分来判定其最大长度。每次check的时候,对区间排序,不断取出不大于m对最大值与最小值求值即可。
然后你就喜提TLE了,笑如果用倍增来代替二分的话能过,不过其实倍增最坏复杂度和二分一样,应该是数据没有刻意来卡倍增。下面先给出倍增的代码。
代码
const int maxn = 5e5+10;
int t,n,m,arr[maxn],tmp[maxn]; ll T;
ll check(int l, int r) {
for (int i = l; i<r; ++i) tmp[i] = arr[i];
sort(tmp+l,tmp+r);
ll sum = 0; int stp = 0;
for (int i = 0; i < m && l+i<r-i-1; i ++ )
sum += (ll)(tmp[l+i]-tmp[r-i-1])*(tmp[l+i]-tmp[r-i-1]);
return sum;
}
int main(){
scanf("%d",&t);
while(t--) {
scanf("%d%d%lld",&n,&m,&T);
for (int i = 0; i<n; ++i) scanf("%d",&arr[i]);
int st = 0, ed = 0, ans = 0;
while(ed<n) {
int len = 1;
while(len) {
if (ed+len<=n && check(st,ed+len)<=T) {
ed += len; len <<= 1;
}
else len >>= 1;
}
++ans;
st = ed;
}
printf("%d\n",ans);
}
return 0;
}
进一步优化
我们在倍增的时候发现一个问题,每次枚举区间的时候,它的长度都是在递增的,而我们每次check都重复的对区间前部进行的排序,所以就有了优化空间。
我们可以每次只对新增的部分排序,因为前面的部分有序,所以就可以像归并排序那样讲两段区间合并成一个区间,这样减少了排序长度,就能获得更快的速度。
代码
const int maxn = 5e5+10;
int t,n,m,arr[maxn],tmp[maxn],tmp2[maxn]; ll T;
bool check(int l, int r, int len) {
for (int i = l; i<r+len; ++i) tmp[i] = arr[i];
sort(tmp+r,tmp+r+len);
int st1 = l, st2 = r, tot = l;
while(st1<r || st2<r+len) {
if (st2>=r+len || (st1<r&&tmp[st1]<=tmp[st2])) tmp2[tot++] = tmp[st1++];
else tmp2[tot++] = tmp[st2++];
}
ll sum = 0;
for (int i = 0; i < m && l+i<r+len-i-1; ++i)
sum += (ll)(tmp2[l+i]-tmp2[r+len-i-1])*(tmp2[l+i]-tmp2[r+len-i-1]);
return sum<=T;
}
int main(){
scanf("%d",&t);
while(t--) {
scanf("%d%d%lld",&n,&m,&T);
for (int i = 0; i<n; ++i) scanf("%d",&arr[i]);
int st = 0, ed = 0, ans = 0;
while(ed<n) {
//cout << st << ' ' << ed << endl;
int len = 1;
while(len) {
if (ed+len<=n && check(st,ed,len)) {
for (int i = ed; i<ed+len; ++i) arr[i] = tmp2[i];
ed += len; len <<= 1;
}
else len >>= 1;
}
++ans;
st = ed;
}
printf("%d\n",ans);
}
return 0;
}