蓝书(算法竞赛进阶指南)刷题记录——CH0601 Genius ACM(二分+倍增+序列合并)

题目:CH0601.
题目大意:给定一串长度为 n n n的序列,要你把序列分成几段,使得每段的SPD值都小于 T T T,求最小段数.其中一段序列的SPD值是指,在这段序列中取出
M M M对数(若不足 M M M堆则尽量多取),使得这 M M M对数每对数 ( a , b ) (a,b) (a,b) ( a − b ) 2 (a-b)^2 (ab)2的和的最大值.
1 ≤ n , m ≤ 5 ∗ 1 0 5 1\leq n,m\leq 5*10^5 1n,m5105.

一段序列的SPD值怎么求?我们可以贪心地让最大差最大,次大差次大…也就是说让这段序列选出的 M M M对数为(最大值,最小值),(次大值,次小值)…

然后我们考虑暴力去做这道题,那么我们就可以枚举.容易发现肯定是取得越多越好,所以我们就枚举一边.每次都判断当前段SPD值是否小于 T T T,判断的时候每次排序,也可以每次都在原来排好的序列中插入数字,时间复杂度 O ( n 2 l o g n ) O(n^2logn) O(n2logn) O ( n 2 ) O(n^2) O(n2).

我们发现,SPD值计算一次最少也要 O ( n ) O(n) O(n),所以我们不可能从SPD值的计算当中着手优化.于是我们考虑如何优化枚举.

显然,这道题求的值具有单调性,我们或许可以二分,但是若设最后的段答案为 a n s ans ans,那么时间复杂度就是 O ( a n s ∗ n log ⁡ n ) O(ans*n\log n) O(ansnlogn),貌似会很慢.

我们考虑换成倍增,每次呈两倍增长要加长的长度,可以得到一个长度 l e n len len,那么接下来要增长的长度不超过 l e n len len,我们就可以再二分增长,得到一个最长长度 l e n len len,这样我们就可以得出答案了.时间复杂度 O ( n log ⁡ 2 n ) O(n\log^2n) O(nlog2n).

我们发现这个算法明显跑不满,但是出题人把这个算法卡掉了.

于是我们发现,上一次排好序的序列实际上这一次是可以直接用的,不需要重新排序.那么就每一次把增加的长度排序,然后与原来的序列合并.

关于时间复杂度,一段区间 [ l , r ] [l,r] [l,r]的倍增时间复杂度为 O ( ∑ i = 1 log ⁡ ( r − l + 1 ) 2 i ∗ log ⁡ ( 2 i ) ) = O ( ( r − l + 1 ) log ⁡ ( r − l + 1 ) ) O(\sum_{i=1}^{\log(r-l+1)}2^i*\log(2^i))=O((r-l+1)\log(r-l+1)) O(i=1log(rl+1)2ilog(2i))=O((rl+1)log(rl+1)),那么也就是说倍增部分的时间复杂度最多是 O ( n log ⁡ n ) O(n\log n) O(nlogn).同理二分也是 O ( n log ⁡ n ) O(n\log n) O(nlogn),总的加起来就是 O ( n log ⁡ n ) O(n\log n) O(nlogn).

那么代码实现如下:

#include<bits/stdc++.h>
  using namespace std;
#define Abigail inline void
typedef long long LL;
const int N=500000;
int n,m,p[N+9],ans;
int l,r;
LL tmp[N+9],order[N+9];
LL K;
LL sqr(LL a){
  return a*a;
}
void merge(int L,int mid,int R){
  int i=L,j=mid,k=L;
  while (i<mid&&j<=R)
    if (order[i]<=order[j]) tmp[k++]=order[i++];
    else tmp[k++]=order[j++];
  while (i<mid) tmp[k++]=order[i++];
  while (j<=R) tmp[k++]=order[j++];
}
bool check(int L,int mid,int R){
  for (int i=mid;i<=R;i++)
    order[i]=p[i];
  stable_sort(order+mid,order+R+1);
  merge(L,mid,R);
  LL sum=0;
  for (int i=1;i<=R-L+1>>1&&i<=m;i++)
    sum+=sqr(tmp[R-i+1]-tmp[L+i-1]);
  if (sum<=K){
    for (int i=L;i<=R;i++)
      order[i]=tmp[i];
    return true;
  }else return false; 
}
Abigail into(){
  l=r=0;ans=0;
  scanf("%d%d%lld",&n,&m,&K);
  for (int i=1;i<=n;i++)
    scanf("%lld",&p[i]);
}
Abigail work(){
  int len=1;
  l=r=1;
  order[l]=p[l];
  while (r<=n)
    if (!len){
      len=1;ans++;
      l=++r;
      order[l]=p[l];
    }else if (r+len<=n&&check(l,r+1,r+len)) {
      r+=len;len<<=1;
      if (r==n) break;
    }else len>>=1;
  if (r==n) ans++;
}
Abigail outo(){
  printf("%d\n",ans);
}
int main(){
  int T=1;
  scanf("%d",&T);
  while (T--){
    into();
    work();
    outo();
  }
  return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值