soda has an integer array a1,a2,…,ana1,a2,…,an. Let S(i,j)S(i,j) be the sum of ai,ai+1,…,ajai,ai+1,…,aj. Now soda wants to know the value below:
∑i=1n∑j=in(⌊log2S(i,j)⌋+1)×(i+j)∑i=1n∑j=in(⌊log2S(i,j)⌋+1)×(i+j)
Note: In this problem, you can consider log20log20 as 0.
Input
There are multiple test cases. The first line of input contains an integer TT, indicating the number of test cases. For each test case:
The first line contains an integer nn (1≤n≤105)(1≤n≤105), the number of integers in the array.
The next line contains nn integers a1,a2,…,ana1,a2,…,an (0≤ai≤105)(0≤ai≤105).
Output
For each test case, output the value.
Sample Input
1 2 1 1
Sample Output
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<cstdio>
#include<iostream>
#define ll long long
using namespace std;
const int N = 100005;
ll 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;
}
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;
}
ans+=num*i;
}
printf("%lld\n",ans);
}
return 0;
}