题目: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;
}