hdu 6304

(找规律)
题意:
  给定一个数列,以及 T T (T<105)个询问。每个询问包含一个数字 n n (n<1018),输出这个数列的前 n n 项和。

这里写图片描述

思路:
  先打表,看看an Sn S n (将数列的前n项和记为 Sn S n )有什么规律。
   n n : 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
  an : 1, 1, 2, 3, 3, 4, 4, 4, 5, 6
   Sn S n : 1,2,4,6,9,13,17,21,26,32
  观察到 an a n 中的数字是连续出现的,但是每个数字出现次数不同。这时找不到 an a n 数字出现次数和n的关系。但是发现 an a n 出现次数和自身这个数字有关: an a n 出现的次数等于 an a n 的lowbit值的位数。(比如3,它的二进制是1 1,lowbit只有一位,故3出现一次)。
  这样一来,虽然给定 n n 的值,我们不能找到an,但是反过来是可以运算的。举例而言,假如 an a n 为5, n n 的值就是(log(lowbit(i))+1)(通过枚举 lowbit(x) l o w b i t ( x ) 的值来求解)。知道这个以后,我们可以准确估计一下 an a n 的上下界,然后通过二分求得 an a n 。时间复杂度: O(Klog(n)) O ( K ∗ l o g ( n ) )
   求出 an a n 后,就想办法求 Sn S n ,由于 an a n 之前的数字肯定小于等于 an a n ,并且是连续出现。我们就可以想办法将这个数列拆成 k k 个子数列,进而再求和。其中第i个子数列中的数字的 lowbit l o w b i t 值都是 i i 。时间复杂度:O(log(n))
   总时间复杂度: O(Klog(n)) O ( K ∗ l o g ( n ) )

关键点:
  想到如何快速找到 an a n 的值。

代码:

#include <cstdio>
#include <algorithm>
#define LL long long

using namespace std;

const LL mod = 1000000007;
const LL inv2 = 500000004;

LL get_idx(LL num) {
    LL ret = 0, x = num;
    for (LL i=0; (1LL<<i)<=num; i++) {
        ret += (i+1) * ((x+1)>>1);
        x /= 2;
    }
    return ret;
}

LL get_sum(LL num) {
    LL ret = 0;
    for (LL i=0; (1LL<<i)<=num; i++) {
        LL n_term = ((num-(1LL<<i)) >> (i+1)) + 1;
        LL fst = (1LL<<i), lst = fst + ((n_term-1) << (i+1));
        LL incre = (i+1) * ((fst + lst) % mod) % mod * (n_term % mod) % mod * inv2 % mod;
        //ret += (i+1) * (fst + lst) * n_term / 2;
        ret = (ret + incre) % mod;
    }
    return ret;
}

int main() {
    //freopen("in.txt", "r", stdin);
    LL n, T;
    scanf("%lld", &T);
    while (T --) {
        scanf("%lld", &n); n --;
        LL l = max(n/2-50, 1LL), r = n/2+50;
        while (l < r) {
            LL mid = (l+r) / 2, idx = get_idx(mid);
            if (idx < n) l = mid + 1;
            else r = mid;
        }
        LL sum = get_sum(l), cur_idx = get_idx(l);
        LL offset = l % mod * (cur_idx - n) % mod;
        LL ans = (sum - offset + mod + 1) % mod;
        printf("%lld\n", ans);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值