杭电多校第一场 Chiaki Sequence Revisited(找规律)

Chiaki Sequence Revisited

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;
}

 

你可能感兴趣的:(ACM-数学)