1. 程式人生 > >杭電多校第一場 Chiaki Sequence Revisited(找規律)

杭電多校第一場 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

 思路:這種題肯定要打個表,打出來表發現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 <stdio.h>
#include <algorithm>

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