HDU 6304多校第一场1007

这是看一个大佬的题解补的题,但是不小心关掉窗口找不到他的出处了,看见的话帮忙标注一下

整体做题思路:找规律->找出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}$$
1122344456
$$a_{11}$$$$a_{12}$$$$a_{13}$$$$a_{14}$$$$a_{15}$$$$a_{16}$$$$a_{17}$$$$a_{18}$$$$a_{19}$$$$a_{20}$$
6788889101011

 

如果我们从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$$12345678
出现次数12131214

我们会发现一个有趣的东西前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;
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值