HDU 6304

3 篇文章 0 订阅
2 篇文章 0 订阅

题目:HDU6304
题意:给定一个序列,计算前n项和,n最大1e18
思路:打表找规律。。。找到规律了还要卡时间(有dalao的规律是一个log就可以了的,也有O(1)过的,真可怕),所以在二分的时候不停的缩上下界才勉强卡过。。。

#include <bits/stdc++.h>

/*
规律:
序列中的数字i(不为1)的出现次数为k + 1, k满足条件—— 2^k 整除 i, maxize k
那么先把第一个出现的1无视,然后从第二个开始,出现次数为
1次的有: 1, 3, 5, 7, 9, ... 公差为2的等差数列
2次  : 2, 6, 10, 14, ...  公差为4的等差数列
3次  : 4, 12, 20, 28, ... 公差为8的等差数列
4次  : 8, 24, 40, ...   公差为16的等差数列
...
*/

using namespace std;
typedef long long ll;
const ll mod = 1e9 + 7;
ll n;
ll b[100];

void read(ll & a)
{
    char ch = getchar();
    a = 0;
    while(ch < '0'  || ch > '9')    ch = getchar();
    while(ch >= '0' && ch <= '9')
    {
        a = a * 10 + ch - '0';
        ch = getchar();
    }
}

ll calc(ll mid)
{//数字mid最后一次出现的项数
    ll res = 0;
    for(ll i = 0; b[i] <= mid; i ++)
        res += ((mid - b[i]) / b[i + 1] + 1) * (i + 1);
    return res + 1;//加回一开始忽略的1
}

int main()
{
//    freopen("in.txt", "r", stdin);
//    freopen("out.txt", "w", stdout);
//    b[1] = b[2] = 1;
//    puts("1: 1\n2: 1");
//    for(int i = 3; i < 100; i ++)
//        printf("%d: %d\n", i, b[i] = b[i - b[i - 1]] + b[i - 1 - b[i - 2]] );
    ll T;  read(T);
    b[0] = 1;
    for(int i = 0; b[i] <= 1e18; i ++)
        b[i + 1] = b[i] << 1;
    while(T --)
    {
        read(n);
        if(n == 1)
        {
            puts("1");
            continue ;
        }
        ll l = max(1ll, n / 2 - 30), r = n / 2 + 30, mid;//群里都说两个log会T,是真的会T,原本是在n/2正负100和60二分,T得很稳
        while(l <= r)
        {
            mid = (l + r) >> 1;
            if(calc(mid) >= n)  r = mid - 1;
            else    l = mid + 1;
        }
        ll m = r + 1;//二分找出a[n] = m;
        ll ans = 1;
        //需要注意下面的b都是没有%过的
        for(ll i = 0; b[i] <= m; i ++)
        {//等差数列求和
            ll k = (m - b[i]) / b[i + 1] + 1;//项数 k
            k %= mod;
            ll sum = b[i] % mod * k % mod + k * (k - 1) / 2 % mod * (b[i + 1] % mod) % mod;//和
            ans = (ans + sum % mod * (i + 1) % mod) % mod;//该序列的数字都会出现 i + 1 次
        }
        ans -= (calc(m) - n) % mod * m % mod;
        printf("%lld\n", (ans % mod + mod) % mod);

    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值