HDU 5358 First One(枚举+尺举法)

题目链接:传送门 

题意:设f(i,j)表示区间[i,j]内元素的和 ,定义 SUM(i,j) = [log2(f(i,j))+1]*(i+j)

求 sigma(sum (i,j)) ( 1<=i<=n,i<=j<=n )

分析: log2(f(i,j))表示f(i,j)转换为2进制的长度,然后我们经过分析log2(f(i,j))+1的值域

为[1,34]然后我们枚举log2(f(i,j))+1的值,例如我们枚举其值为k,对于一个k我们找到所有满足

条件的区间(i,j),这个条件的代数表达为 2^(k-1)<= f(i,j) <=2^k-1;

因此我们需要再枚举一个区间的左端点,对于一个给定的左端点,因为f(i,j)在给定i的情况下单调,

我们可以用尺举发求得一个区间[l,r],使得区间内的j (l<=j<=r)都瞒住sum(i,j)+1=k;

然后区间(i+j)的和可以表示为 i*(r-l+1) + (r+l)*(r-l+1)/2;


代码如下:

#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>
using namespace std;

const int maxn = 1e5+10;

typedef long long LL;

LL sum[maxn];

int main()
{
    int t,n;
    scanf("%d",&t);
    while(t--){
        scanf("%d",&n);
        sum[0]=0;
        for(int i=1;i<=n;i++){
            LL x;
            scanf("%I64d",&x);
            sum[i]=sum[i-1]+x;
        }
        LL ans = 0;
        for(LL k = 1;k<=34;k++){
            LL l=1,r=0;
            LL lmax = 1LL<<(k-1),rmax=(1LL<<k)-1;
            if(k==1) lmax = 0;
            for(LL i=1;i<=n;i++){
                l=max((LL)i,l);
                while(l<=n&&sum[l]-sum[i-1]<lmax) l++;
                r=max(l-1,r);
                while(r+1<=n&&sum[r+1]-sum[i-1]<=rmax&&sum[r+1]-sum[i-1]>=lmax)r++;
                if(l>r) continue;
                ans=ans+(i*(r-l+1)+(r+l)*(r-l+1)/2)*k;
            }
        }
        printf("%I64d\n",ans);
    }
    return 0;
}


你可能感兴趣的:(HDU 5358 First One(枚举+尺举法))