这是看一个大佬的题解补的题,但是不小心关掉窗口找不到他的出处了,看见的话帮忙标注一下
整体做题思路:找规律->找出an->求和
首先我们对公式进行打表
$$a_n=\begin{cases}1 & n = 1,2 \\ a_{n - a_{n-1}} + a_{n-1 - a_{n-2}} & n \ge 3\end{cases}$$
$$a_1$$ | $$a_2$$ | $$a_3$$ | $$a_4$$ | $$a_5$$ | $$a_6$$ | $$a_7$$ | $$a_8$$ | $$a_9$$ | $$a_{10}$$ |
1 | 1 | 2 | 2 | 3 | 4 | 4 | 4 | 5 | 6 |
$$a_{11}$$ | $$a_{12}$$ | $$a_{13}$$ | $$a_{14}$$ | $$a_{15}$$ | $$a_{16}$$ | $$a_{17}$$ | $$a_{18}$$ | $$a_{19}$$ | $$a_{20}$$ |
6 | 7 | 8 | 8 | 8 | 8 | 9 | 10 | 10 | 11 |
如果我们从a2开始我们会发现ai的值会出现以下规律:
出现1次的值有:1,3,5,7,9,11
出现2次的值有:2,6,10
出现3次的值有:4
...............以此类推
我们可以看出每列都是一个等差数列,他们分别以2^(i - 1)为首项,以2^i为公差,这对后面的求和有很大的帮助(ps:这里的i是出现的次数)
接着我们继续将ai的值出现的次数进行打表整理
$$a_i$$ | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
出现次数 | 1 | 2 | 1 | 3 | 1 | 2 | 1 | 4 |
我们会发现一个有趣的东西前2^i项的次数和是前2^(i - 1)项的次数和复制一遍后并在最后一个数+1后相加所得
e.g:前2^2项的次数和即:a1、a2、a3、a4的次数和是a1、a2的次数和复制一遍在最后一个数后面+1再相加
a1和a2的次数和为 1 + 2 a1、a2、a3、a4的次数和为1 + 2 + 1 + (2 + 1) 即1 + 2 + 1 + 3
这样我们可以容易的得到前2^i项的次数和
我们可以这样实现
num[0] = 1;
for(int i = 1; i <= 62; i++)//62后超过范围
num[i] = num[i - 1] * 2 + 1;
这个规律就像是每一个数字都可以用n个2^i的值相加所得,因此我们就可以确定an的值
ll a = 0;
for(int i = 62; i >= 0; i--) {
while(num[i] <= n) {
n -= num[i];
a += (1LL << i);
}
}
我们会发现an的值会出现在多个项里面,我们要把最后an不稳定的次数给求出来并在最后加上去,这里我们只要用总数n减去比an的值小1的值的位置 + 1,即:n - (pos + 1)为an的值会出现的个数(+1是因为我们是从a2开始的)
ll get_counter(ll a) {
if(a == 0)
return 1;
ll pos = 0;
for(int i = 62; i >= 0; i--) {
while((1LL << i) <= a) {
a -= (1LL << i);
pos += num[i];
}
}
return pos + 1;
}
ll counter = n - get_counter(a - 1);
最后我们只要对每个次数数列的首项进行枚举求和就可以得出答案
最后贴上完整代码
#include <iostream>
#include <cstdio>
#include <fstream>
#include <algorithm>
#include <cmath>
#include <deque>
#include <vector>
#include <queue>
#include <string>
#include <cstring>
#include <map>
#include <stack>
#include <set>
#include <list>
#define INF 0x3f3f3f3f
#define maxn 105000
#define maxnn 6000
#define juzheng 300
#define line cout << "-------------------------" << endl;
#define PI acos(-1.0)
#define mem(a,b) memset(a,b,sizeof(a))
#define fill_(a,b,n) fill(a,a + n,b)
#define esp 1e-9
#define ri(n) scanf("%d",&n)
#define ri2(a,b) scanf("%d %d",&a,&b)
#define ri3(a,b,c) scanf("%d %d %d",&a,&b,&c)
#define rd(n) scanf("%lf",&n)
#define rd2(a,b) scanf("%lf %lf",&a,&b)
#define rd3(a,b,c) scanf("%lf %lf %lf",&a,&b,&c)
#define rl(n) scanf("%lld",&n)
#define pr(n) cout << n << endl
#define ll long long
#define int64 __int64
using namespace std;
const ll mod = 1e9 + 7;
//Date:2018-7-24
//Author:HarryBlackCat
ll num[maxn],inv,ans,counter;
void init() {
ans = 0;
inv = mod / 2 + 1;
}
ll get_a(ll n) {
if(n == 1)//特判第一个直接返回
return 1;
n--;//从a2开始
ll a = 0;
for(int i = 62; i >= 0; i--) {
while(num[i] <= n) {
n -= num[i];
a += (1LL << i);
}
}
return a;
}
ll get_counter(ll a) {
if(a == 0)//特判第一个直接返回
return 1;
ll pos = 0;
for(int i = 62; i >= 0; i--) {
while((1LL << i) <= a) {
a -= 1LL << i;
pos += num[i];
}
}
return pos + 1;//我们是从a2开始的所以要++
}
int main() {
//cin.sync_with_stdio(false);//降低cin,cout时间
num[0] = 1;
for(int i = 1; i <= 62; i++)
num[i] = num[i - 1] * 2 + 1;//求前2^i项的次数和
int t;
ll n;
while(~ri(t)) {
while(t--) {
init();//初始化
rl(n);
ll a = get_a(n);
counter = n - get_counter(a - 1);//counter是an的的值出现的次数
for(int i = 1; (1LL << (i - 1)) < a; i++) {
ll a1 = 1LL << (i - 1);//首项
ll d = 1LL << i;//公差
ll n = (a - a1) % d == 0 ? (a - a1) / d : (a - a1) / d + 1;//求次数数列的项数
ll an = a1 + d * (n - 1);//等差数列求an
ll sum = (((a1 % mod + an % mod) % mod) * (n % mod)) % mod * inv % mod;//等差数列求和公式,逆元处理
ans = (ans % mod + (sum % mod * i % mod) % mod) % mod;//将答案相加注意mod
// if(!((a - a1) % d))//这里是加上an出现的次数,整除公差时已加上不用再加
// ans = (ans % mod + (counter % mod * a % mod) % mod) % mod;
}
ans = (ans % mod + (counter % mod * a % mod) % mod) % mod;//这里是加上an出现的次数
pr(ans + 1);//别忘了把a1加上去
}
}
return 0;
}