1. 程式人生 > >藍書(演算法競賽進階指南)刷題記錄——Genius ACM

藍書(演算法競賽進階指南)刷題記錄——Genius ACM

題目大意:給定一串序列,要你把序列分成幾段,使得每段的SPD值都小於T,求最小段數.其中一段序列的SPD值是指,在這段序列中取出M對數(若不足M堆則儘量多取),使得這M對數每對數(a,b)的(a-b)^2的和的最大值.

一段序列的SPD值怎麼求?我們可以貪心地讓最大差最大,次大差次大...也就是說讓這段序列選出的M對數為(最大值,最小值),(次大值,次小值)...

然後我們考慮暴力去做這道題,那麼我們就可以列舉.容易發現肯定是取得越多越好,所以我們就列舉一邊.每次都判斷當前段SPD值是否小於T,判斷的時候每次排序,也可以每次都在原來排好的序列中插入數字,時間複雜度O(n^2logn)O(n^2).

我們發現,SPD值計算一次最少也要O(n),所以我們不可能從SPD值的計算當中著手優化.於是我們考慮如何優化列舉.

顯然,這道題求的值具有單調性,我們或許可以二分,但是若設最後的段數位ans,那麼時間複雜度就是O(ans*nlogn).

我們發現ans一大,二分就不行了.

我們考慮換成倍增,每次呈兩倍增長要加長的長度,可以得到一個長度len,那麼接下來要增長的長度不超過len,我們就可以再二分增長,得到一個最長長度len,這樣我們就可以得出答案了.時間複雜度O(nlog^2n).

我們發現這個演算法明顯跑不滿,但是出題人把這個演算法卡掉了.

於是我們發現,上一次排好序的序列實際上這一次是可以直接用的,不需要重新排序.那麼就每一次把增加的長度排序,然後與原來的序列合併.

關於時間複雜度,一段區間[l,r]的倍增時間複雜度為O(\sum_{i=1}^{log(r-l+1)}2^i*log(2^i))=O((r-l+1)log(r-l+1)),那麼也就是說倍增部分的時間複雜度最多是O(nlogn)

.同理二分也是O(nlog(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;
}