hihocoder1384/CH0601 Genius ACM[贪心+倍增+归并排序]
关于lyd给的倍增方法,即从当前枚举向后的$2^k$长度($k$从$1$开始),如果可行就将$k$加一以扩大范围,不可行时将范围不断减半直至$0$。
举个例子,假设当下在1,目标答案是13,那么枚举的范围变化情况是$2$,$4$,$8$,$16$(不行,且范围开始缩小),$12$,$14$(不行),$13$,$13$(范围缩小至0)。
并没有看出这样倍增有什么好处。复杂度可证也是$O(logN)$的,但是不是会带个2左右的常数么。。具lyd所说,当目标答案位置较近时会加快效率。
但是这不影响整体复杂度啊。。带着不很理解的态度看了例题才明白这种倍增姿势相较于二分或者从大到小二倍增的优势所在。
题意:数列$N \leqslant 500000$,求划分最少区间使得每段区间任选$M$对数字(不重复选,不够$M$对的时候能选多少选多少)的差的平方之和小于$K$。
首先很容易证明(cai ce)到对于一列数的最大的上述价值就是将最大数减最小数平方加上次大数减次小数平方加上......也就是排序后头尾相配。可以微调法证明任意一种其他策略不会更优。此贪心为关键之一。
然后因为满足要求的一段区间显然越长越好,所以从起点开始拓展,拓展到最远的地方记下来,反复接替拓展,一定是最小区间数。又因为答案随数列增长是有单调性的,所以可以二分检查找到最远的符合要求的右端点。
每次check时候对区间进行排序,加上二分以及区间数的复杂度,最坏$O(N^2log^2N)$。
可以发现,二分时候每次排序都是一个$O(NlogN)$,因为最坏可能答案在比较靠右的位置。这个时候,lyd给出的倍增方案就派上用场了。
lyd书上描述的倍增方法,总是将复杂度限制在$log($答案区间的长度$)$。假若我们采用这种倍增,那么每次枚举的区间最长长度假设为$K$,则找到这样一个区间的复杂度是$O(Klog^2K)$。(倍增一个$logK$,每次排序一个$KlogK$)而不是原来完整的N。
那么,每个区间的复杂度累加起来,不会超过$O(Nlog^2N)$。很容易证。所以通过限制枚举次数在答案对数内,累加起来就比原来少一个log。这就是这种倍增优势。
但是$O(Nlog^2N)$仍然过不了。考虑到每次都要排序,前面已经可行的区间又被拉进来排了一次,显然浪费时间。于是可以只对当前试探的这段区间排序后,和原来已排好序的两个数列归并。归并完求代价,判断是否满足要求。
每次只对新的一小段排序,总体累加起来每次拓展的排序复杂度是$O(KlogK)$的(对于枚举过头了的区间,虽然不断缩小不断重复排序,但由于每次的排序复杂度折半,总体不会超过原来大区间的两倍)。
而$logK$次倍增每次复制需要$O(K)$,所以也是$O(KlogK)$.
最后,总体累加,复杂度$O(NlogN)$。
这题给予我们几个启发:二分和倍增都可用于有单调性的查找,有时候两者没有什么区别,但有时的check函数复杂度和答案位置有关,倍增可以通过限制此条件使得总体复杂度优化掉。
代码写起来的话还是很少的。
WA记录:
- line61智障没考虑边界。
- line49笔误。。
- line59每次初始值。。哎可能是我倍增没有操作好
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<cmath> 5 #include<algorithm> 6 #include<queue> 7 #define dbg(x) cerr<<#x<<" = "<<x<<endl 8 #define _dbg(x,y) cerr<<#x<<" = "<<x<<" "<<#y<<" = "<<y<<endl 9 using namespace std; 10 typedef long long ll; 11 template<typename T>inline char MIN(T&A,T B){return A>B?A=B,1:0;} 12 template<typename T>inline char MAX(T&A,T B){return A<B?A=B,1:0;} 13 template<typename T>inline T _min(T A,T B){return A<B?A:B;} 14 template<typename T>inline T _max(T A,T B){return A>B?A:B;} 15 namespace io{ 16 const int SIZE = (1 << 21) + 1; 17 char ibuf[SIZE], *iS, *iT, obuf[SIZE], *oS = obuf, *oT = oS + SIZE - 1, c, qu[55]; int f, qr; 18 #define gc() (iS == iT ? (iT = (iS = ibuf) + fread (ibuf, 1, SIZE, stdin), (iS == iT ? EOF : *iS ++)) : *iS ++) 19 inline void flush (){fwrite (obuf, 1, oS - obuf, stdout);oS = obuf;} 20 inline void putc (char x){*oS ++ = x;if (oS == oT) flush ();} 21 template <class I> 22 inline void read(I &x) {for (f = 1, c = gc(); c < '0' || c > '9'; c = gc()) if (c == '-') f = -1; 23 for (x = 0; c <= '9' && c >= '0'; c = gc()) x = x * 10 + (c & 15); x *= f;} 24 template <class I> 25 inline void print (I x){ 26 if (!x) putc ('0'); if (x < 0) putc ('-'), x = -x;while(x) qu[++ qr] = x % 10 + '0', x /= 10;while (qr) putc (qu[qr--]);} 27 struct Flusher_ {~Flusher_(){flush();}}io_flusher_; 28 } 29 using io::read; 30 using io::putc; 31 using io::print; 32 const int N=500000+7; 33 int a[N],tmp[N],b[N],c[N];//c:临时排序数组 b:已归并好的数组 tmp:临时归并数组 34 ll k; 35 int T,n,m,ans; 36 37 inline void Merge(int L,int R,int r){ 38 int i=L,j=R+1; 39 for(register int k=L;k<=r;++k) 40 if(b[i]<c[j]&&i<=R||j>r)tmp[k]=b[i++]; 41 else tmp[k]=c[j++]; 42 } 43 inline ll calc(int L,int R,int r){ 44 if(r==R)return k+1;//_dbg(L,R),dbg(r); 45 for(register int i=R+1;i<=r;++i)c[i]=a[i]; 46 sort(c+R+1,c+r+1); 47 Merge(L,R,r); 48 ll ret=0;//for(register int i=L;i<=r;++i)printf("%d ",tmp[i]);puts(""); 49 for(register int i=L;i<=L+_min(m,(r-L+1>>1))-1;++i)ret+=(tmp[r-i+L]-tmp[i])*1ll*(tmp[r-i+L]-tmp[i]); 50 return ret; 51 } 52 53 int main(){//freopen("test.in","r",stdin);//freopen("test.out","w",stdout); 54 read(T);while(T--){ 55 read(n),read(m),read(k); 56 for(register int i=1;i<=n;++i)read(a[i]); 57 ans=0;int L=1,R=1,p,r; 58 while(L<=n){ 59 p=1;b[L]=a[L]; 60 while(p){ 61 if(calc(L,R,r=_min(n,R+p))<=k){ 62 for(register int i=L;i<=r;++i)b[i]=tmp[i]; 63 R=r,p<<=1; 64 } 65 else p>>=1; 66 } 67 L=R+1,R=L,++ans; 68 } 69 print(ans);putc('\n'); 70 } 71 return 0; 72 }