HDU 5358 多校第6场 First One
First One
Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 131072/131072 K (Java/Others)Total Submission(s): 672 Accepted Submission(s): 193
Problem Description
soda has an integer array a1,a2,…,an .
Let S(i,j) be
the sum of ai,ai+1,…,aj .
Now soda wants to know the value below:
∑i=1n∑j=in(⌊log2S(i,j)⌋+1)×(i+j)
Note: In this problem, you can considerlog20 as
0.
Note: In this problem, you can consider
Input
There are multiple test cases. The first line of input contains an integer T ,
indicating the number of test cases. For each test case:
The first line contains an integern (1≤n≤105) ,
the number of integers in the array.
The next line containsn integers a1,a2,…,an (0≤ai≤105) .
The first line contains an integer
The next line contains
Output
For each test case, output the value.
Sample Input
1 2 1 1
Sample Output
12
Source
首先题意不是啥问题,就是给你个数组,让你求那个式子,s表示i到j的和。
比赛的时候想了个带二分的,就是统计log2可能出现的可能,一共同拥有34种,然后求一个前缀和,前缀和是递增的,能够二分。就写了
复杂度没算好,以为33*n*log(n)能过。结果跪了一下午,昨天就这么愉快的跪了
赛后题解上说用俩指针扫一遍,不得不吐槽一下。MD题解都是英文的。
联想到一次bestcoder 有一个题是求一共序列中两个数的和模上一个数最大,我以前的做法就是排序二分。而正解就是俩指针扫一遍,然而我没有记住
这个题就卡这个log(n)然而我就是过不去,诶。还是思维被限制住了
今天上午搞了个33 * n的
思路:
把数组处理成 前缀和,把log分类,有35种情况,题中吧log(0) 规为0。须要特判,题中给的最大的sum值为10^10,所以有35种情况,
那么就分情况讨论,一层循环。里面就是枚举n。然后找到两个值一个是sum[i-1] + 2^j的位置。另一个是sum[i-1]+2^(j+1)的位置,这段区间内的log值就全为j,用等差数列求和公式就能瞬间算出来这段区间的值,并且仅仅须要两个标记扫一遍。为何能够仅仅扫一遍呢,sum数组的值是递增的。须要找的值也是递增的。所下面次找的时候仅仅须要在上次寻找的后面继续接上就OK
/**************************************** ** 2015 Multi-University Training Contest 6 ** 1006 First One ** HDU 5358 ** by calamity_coming **************************************/ #include <bits/stdc++.h> using namespace std; typedef long long ll; const int MAX_LONG = 1E5; ll a[MAX_LONG + 10]; ll sum[MAX_LONG + 10]; ll cf2[40]; void init() { ll k = 1; for(int i=0; i<=34; ++i) { cf2[i] = (k<<(i)); } } int main() { init(); int t; scanf("%d",&t); while(t--) { int n; scanf("%d",&n); sum[0] = 0; for(int i=1; i<=n; ++i) { scanf("%I64d",&a[i]); sum[i] = sum[i-1] + a[i];//前缀和处理 } sum[n+1] = 1E17; ll ans = 0; //0 for(int i=1; i<=n; ++i)//0比較特殊。要特殊的干 { int p = i; while(sum[p]==sum[i-1] && p<=n+1) { ++p; } --p; if(p>=i) { ans += (ll)(i*3+p)*(ll)(p-i+1)/2; } } for(int j=0; j<=33; ++j)//这是每一个情况 { int p1 = 0,p2 = 0;//用两个指针扫一遍 for(int i=1; i<=n; ++i)//当i递增时,新的p1,p2一定在后面 { ll adc = sum[i-1] + cf2[j]; while(sum[p1]<adc && p1<=n) { ++p1; } adc = sum[i-1] +cf2[j+1]; while(sum[p2]<adc && p2<=n+1) { ++p2; } --p2; if(p2>=p1 && p2) { ans += (j+1)*(ll)(i*2+p1+p2)*(ll)(p2-p1+1)/2; } } } printf("%I64d\n",ans); } return 0; }