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