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=1nj=in(log2S(i,j)+1)×(i+j)

Note: In this problem, you can consider  log20  as 0.
 

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 integer  n   (1n105) , the number of integers in the array.
The next line contains  n  integers  a1,a2,,an   (0ai105) .
 

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;
}




  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值