Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 1624 Accepted Submission(s): 437
Problem Description
Chiaki is interested in an infinite sequence a1,a2,a3,..., which is defined as follows:
an={1an−an−1+an−1−an−2n=1,2n≥3
Chiaki would like to know the sum of the first n terms of the sequence, i.e. ∑i=1nai. As this number may be very large, Chiaki is only interested in its remainder modulo (109+7).
Input
There are multiple test cases. The first line of input contains an integer T (1≤T≤105), indicating the number of test cases. For each test case:
The first line contains an integer n (1≤n≤1018).
Output
For each test case, output an integer denoting the answer.
Sample Input
10 1 2 3 4 5 6 7 8 9 10
Sample Output
1 2 4 6 9 13 17 21 26 32
Source
2018 Multi-University Training Contest 1
思路:这种题肯定要打个表,打出来表发现2连续出现2次,然后4连续出先了3次,8连续出现了4次,16连续出现了5次,32连续出现了6次.....,3连续出现了1次,6连续出现了2次,12连续出现了3次......比赛的时候就发现了这个规律。听了大佬在B站上的直播后,发现规律是这样的:
除去1
出现1次的: 3,5,7,9,11,13...... 公差为2的等差数列
出现2次的:2 ,6,10,14,18....... 公差为4的等差数列
出现3次的:4,12,20,28,36...... 公差为8的等差数列
出现4次的:8 ,24,40,56,72..... 公差为16的等差数列
。。。。。可得出x出现的次数为2进制形式最后一个1在从右往左数第几位
根据以上规律我们可以做这道题了,首先二分出来第n个数是几,然后根据这个数x和等差数列前n项公式求和,具体细节看代码
#include
#include
using namespace std;
const int mod = 1000000007;
typedef long long ll;
ll a[100];
ll lowbit(ll num)
{
ll ans = 1;
while(num % 2 == 0) {
ans++;
num /= 2;
}
return ans;
}
ll q_pow(ll base, ll b){
ll res = 1;
while(b){
if(b & 1) res = res * base % mod;
base = base * base % mod;
b >>= 1;
}
return res;
}
int main(void)
{
int T;
scanf("%d",&T);
//a数组存的是等差数列的首项
a[1] = 3;a[2] = 2;
int ans[6];
ans[1] = 1,ans[2] = 2,ans[3] = 4,ans[4] = 6,ans[5] = 9;
//把2的倍数预处理出来
for(int i = 3; i <= 61; i++) {
a[i] = a[i - 1] * 2;
}
while(T--) {
ll inv2 = q_pow(2,mod - 2); //2的逆元
ll n;
scanf("%lld",&n);
if(n <= 5) {
printf("%d\n",ans[n]);
continue;
}
//这里如果直接把l r分别初始化为1 1e18时,会T
//所以要优化一下,通过打表发现a数组对应的第n项在n/2左右
ll l = max(1LL,n / 2 - 30), r = min(n , n / 2 + 30);
//L为这个数在a数列里首次出现的位置
//R为这个数在a数列里最后一次出现的位置
//比如8的L为13,R为16
ll L,R,cnt,i,temp,mid,num;
while(l <= r) {
//二分原理
//假设第n个数对应的是mid,得到mid的L和R,再判断
//n与L和R的关系
mid = (l + r) >> 1;
if(mid == 1) {L = 1,R = 2;}
else if(mid == 2) {L = 3,R = 4;}
else {
//这里的cnt就是求的R,temp为公差
cnt = 2;temp = 2;i = 1;
while(a[i] <= mid) {
//加上第i个等差数列里小于等于mid的数出现的次数
cnt += ((mid - a[i]) / temp + 1) * i;
i++;
temp *= 2;
}
R = cnt;
//R减去mid出现的次数加1为L
L = cnt - lowbit(mid) + 1;
}
if(L <= n && R >= n) {
num = mid;
break;
}
else if(n < L) r = mid - 1;
else l = mid + 1;
}
//求和
ll sum = 2;
i = 1;
temp = 2;
//我们先求出前L-1项的和,就是num第一次出现前的所有数求和
while(a[i] <= num - 1) {
ll _cnt = (num - 1 - a[i]) / temp;//第i歌等差数列小于等于num-1的项数
ll last = (a[i] % mod + (_cnt % mod * temp % mod) % mod) % mod;//求最后一项
last %= mod;
//求和公式求和,求得和还要乘i,因为出现了i次
sum = (sum + (a[i] + last) % mod * (_cnt % mod + 1) % mod * inv2 % mod * i % mod) % mod;
sum %= mod;
//公差扩大2倍
temp *= 2;
i++;
}
//最后再加上从L到n这 (n - L + 1) 个num
sum = (sum + (((n - L + 1) % mod * num % mod) % mod) % mod) % mod;
sum %= mod;
printf("%lld\n",sum);
}
return 0;
}