HDU 5358 尺取法+枚举
题意:给一个数列,按如下公式求和。
分析:场上做的时候,傻傻以为是线段树,也没想出题者为啥出log2,就是S(i,j) 的二进制表示的位数。只能说我做题依旧太死板,让求和就按规矩求和,多考虑一下就能发现这个题目应该是另想办法解决的,类似于改代码的题目,直接告诉你C++代码,让你从TLE改成AC,其实真正让你改的是算法,全身都要变。
看了题解,终于明白这道题目的正解算法:
因为S(i,j)的位数在一定范围内是一样的,所以我们可以枚举位数1~35(顶多是2^34),怎么计算(i+j)?继续枚举起点k,然后找满足位数是所枚举的位数的区间(l,r)即(k,l)~(k,r)都是二进制位数为t的,然后统一计算(k,l)~(k.r)的和,其实就是一个l~r的等差序列(r + l)*(r - l + 1) / 2,一个是k的(r-l+1)倍。一直枚举到位数大于数列全部的和。
PS:枚举区间的时候用的是尺取法,很容易懂,就是从起点k开始,直到有不满足的条件,否则l++,然后r直接从l开始,其实计算的时候一般能想得到。
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 using namespace std; 5 #define LL long long 6 #define maxn 101010 7 LL pow2l[50],pow2r[50],s[maxn]; 8 int main() 9 { 10 for(int i=1; i<=40; i++) 11 { 12 pow2l[i]=(1ll<<i); 13 pow2r[i]=((1ll<<(i+1))-1); 14 } 15 pow2l[0]=0; 16 pow2r[0]=1; 17 LL ncase,i,j,n,x; 18 scanf("%I64d",&ncase); 19 while(ncase--) 20 { 21 scanf("%d",&n); 22 for(i=1; i<=n; i++) 23 { 24 scanf("%I64d",&x); 25 s[i]=s[i-1]+x; 26 } 27 LL ans=0; 28 for(i=1; i<=35; i++) 29 { 30 if(s[n] < pow2l[i-1]) 31 break; 32 LL l=1,r=0,temp=0; 33 for(j=1; j<=n; j++) 34 { 35 l = max(l,j); 36 while(l <= n && s[l] - s[j-1] < pow2l[i-1]) 37 l++; 38 r = max(r,l - 1); 39 while(r < n && s[r+1] - s[j-1] >= pow2l[i-1] && s[r+1]-s[j-1] <= pow2r[i-1]) 40 r++; 41 if(r >= l) 42 temp+=(r-l+1)*j+(r+l)*(r-l+1)/2; 43 } 44 ans += temp * i; 45 } 46 printf("%I64d\n",ans); 47 } 48 return 0; 49 }
人生就像心电图,想要一帆风顺,除非game-over