1 2 1 1
12
题意:给你一个整数序列a1,a2,a3,…,an,要求求出
的值,S(i,j)表示ai+ai+1+ai+2+…+aj
解题报告:观察式子可以知道
的值是x的二进制表示的位数。
比如x=5,它的二进制表示是101,将x=5代入上式,可得3,正是101的位数
另外题目又规定n的大小以及ai的大小都是100000以内的,所以,二进制位数最多34位,即
又因为该题的时间复杂度至多为O(nlogn),所以我们只需枚举位数k,再利用尺取法求出对应的i、j值即可。尺取法的时间复杂度是O(n)的。不知道尺取法的可以参考一下网址“算法初步——尺取法“,最好下载来看,毕竟ppt里有动画,看着比较直观。本来想要弄个学习笔记的,但是貌似CSDN不支持动画。
每对应一个i值,利用尺取法求出满足条件的j值的范围(a<=j<=b),则
i+j=(i+a)+(i+a+1)+(i+a+2)+…+(i+b)=(b-a+1)*i+(a+b)*(b-a+1)/2
换句话说,枚举位数i:1~35。对于每一位i找到区间[x,y],使得S(x,y)的二进制表示的位数等于i,此时的值为i*(x+y)。那么对于每一个i,怎么找出所有符合条件的区间[x,y]?1~n枚举起点x,那么y会在一段范围[l,r]内满足条件。下次x变成x+1,即起点x向右移位,那么现在要找的y的区间为[l',r'],l'不至于小于l同样r'不至于小于r。这样可以用O(n)的复杂度找出所有区间使得区间和的二进制表示的位数为枚举的i。
以下是AC代码 有什么不懂的地方可以提出来
需要指出的一点是该方法仅用G++提交才能AC,若C++仍为TLE,据说是因为G++的输入输出比较快
#include<stdio.h> const int N = 100005; __int64 l[50],r[50],s[N],ans,a,b,num; int main() { int t,i,j,n,x; for(i=1;i<34;i++) { l[i]=(1ll<<i); r[i]=((1ll<<(i+1))-1); } l[0]=0;r[0]=1; scanf("%d",&t); while(t--) { scanf("%d",&n); for(i=1;i<=n;i++) { scanf("%d",&x); s[i]=s[i-1]+x;//求出前i项的和,则s[j]-s[i-1]即为下标i到j部分的总和 } ans=0; for(i=1;i<35;i++)//枚举位数 { if(s[n]<l[i-1]) break; a=1,b=num=0; for(j=1;j<=n;j++) { a=(a>j?a:j); while(a<=n&&s[a]-s[j-1]<l[i-1])//找出满足当前位数的总和的最小下标 a++; b=(b>a-1?b:a-1); while(b+1<=n&&s[b+1]-s[j-1]>=l[i-1]&&s[b+1]-s[j-1]<=r[i-1])//找出满足当前位数的总和的最大下标 b++; if(b>=a) num+=(b-a+1)*j+(b+a)*(b-a+1)/2;//(j+a)+(j+a+1)+(j+a+2)+…+(j+b)=(b-a+1)*j+(a+b)*(b-a+1)/2 } ans+=num*i; } printf("%I64d\n",ans); } return 0; }
欢迎大家指点
菜鸟成长记