藍書(演算法競賽進階指南)刷題記錄——Genius ACM
阿新 • • 發佈:2018-12-12
題目大意:給定一串序列,要你把序列分成幾段,使得每段的SPD值都小於T,求最小段數.其中一段序列的SPD值是指,在這段序列中取出M對數(若不足M堆則儘量多取),使得這M對數每對數(a,b)的的和的最大值.
一段序列的SPD值怎麼求?我們可以貪心地讓最大差最大,次大差次大...也就是說讓這段序列選出的M對數為(最大值,最小值),(次大值,次小值)...
然後我們考慮暴力去做這道題,那麼我們就可以列舉.容易發現肯定是取得越多越好,所以我們就列舉一邊.每次都判斷當前段SPD值是否小於T,判斷的時候每次排序,也可以每次都在原來排好的序列中插入數字,時間複雜度或.
我們發現,SPD值計算一次最少也要O(n),所以我們不可能從SPD值的計算當中著手優化.於是我們考慮如何優化列舉.
顯然,這道題求的值具有單調性,我們或許可以二分,但是若設最後的段數位ans,那麼時間複雜度就是.
我們發現ans一大,二分就不行了.
我們考慮換成倍增,每次呈兩倍增長要加長的長度,可以得到一個長度len,那麼接下來要增長的長度不超過len,我們就可以再二分增長,得到一個最長長度len,這樣我們就可以得出答案了.時間複雜度.
我們發現這個演算法明顯跑不滿,但是出題人把這個演算法卡掉了.
於是我們發現,上一次排好序的序列實際上這一次是可以直接用的,不需要重新排序.那麼就每一次把增加的長度排序,然後與原來的序列合併.
關於時間複雜度,一段區間[l,r]的倍增時間複雜度為,那麼也就是說倍增部分的時間複雜度最多是
那麼程式碼實現如下:
#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; }