1. 程式人生 > >HDU 6304 2018 HDU多校第一場 Chiaki Sequence Revisited(二分+倍增規律)

HDU 6304 2018 HDU多校第一場 Chiaki Sequence Revisited(二分+倍增規律)

Chiaki Sequence Revisited

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 1717    Accepted Submission(s): 465

Problem Description

Chiaki is interested in an infinite sequence a1,a2,a3,..., which is defined as follows: 

                                                         a_n=\begin{cases}1 & n = 1,2 \\ a_{n - a_{n-1}} + a_{n-1 - a_{n-2}} & n \ge 3\end{cases}

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 (10^9+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

大致題意:告訴你數列的遞推公式,讓你求和……

首先,這種下標上面有前幾項的數列,基本上不要去想用矩陣快速冪解決。還是想想規律吧……網上很多什麼lowbit的,其實感覺有點誤導,畢竟這個lowbit不是樹狀陣列的lowbit,而是重新改編定義的lowbit。規律的話,首先列出前幾項:

1,1,2,2,3,4,4,4,5,6,6,7,8,8,8,8,9,10,10,11,12,12,12,13,14,14,15,16,16,16,16,16,17……

已經寫出來了這麼多了……我們可以發現,如果不看第一個1,1,3,5,7,9,11,13,15,17都只出現了一次,2,6,10,14都只出現了兩次,4,12,20,28都只出現了3次,8,24,40,56都只出現了4次……也就是說二進位制下末位的0的個數加一對應了這個數字出現的次數,而且出現次數相同的數字構成等差數列,第i個等差數列的公差是2^i。如果我們能夠確定出現的最大數字,那麼我們就能夠利用等差數列的求和公式計算出總共的和。

於是,我們考慮二分這個最大的數字。有了最大的數字後,同樣的,利用上面發現的規律計算總共出現了多少個數字。經過觀察可以發現這個總數其實是 \sum \frac{mid+2^{i-1}}{2^i}*i ,log倍增上去就可以求和,之後與n比較即可知道出現的最大數字。經過查資料,我們還可以發現一個更簡單的系統自帶函式,這個總數其實就是2*mid - __builtin_popcount(mid),如此O(1)求出和,否則用O(log)的方式還要實現縮小二分範圍。

知道最大值之後,就是等差數列就和,首項加末項乘以項數除以二。設t為每個數列的數字個數,n為出現的最大數字,i為出現次數,那麼對於一個公差為2^i的等差數列,它的和就是i*2^{i-1}*t^2,這個t為\frac{n+2^{i-1}}{2^i}。最後還會剩下一些位置空的,就用n+1來填滿即可。具體見程式碼:

#include<bits/stdc++.h>
#define LL long long
#define ULL long long

using namespace std;

const int mod = 1e9+7;

int T;
LL n;

int cnt(LL x)
{
    LL ret=0;
    while(x)
    {
        x-=x&-x;
        ret++;
    }
    return ret;
}

LL sum(LL x)
{
    return 2*x-cnt(x);
}

LL power(int x)
{
    return (1LL<<x);
}

LL sqr(LL x)
{
    return x*x%mod;
}

LL calc(LL ans,LL n)
{
    LL m=n,ret=0;n=ans;
    for(LL i=1;i<62;i++)
    {
        LL tmp=(n+power(i-1))/power(i);
        ret+=power(i-1)%mod*sqr(tmp%mod)%mod*i%mod;
    }
    ret+=((m-sum(ans)-1)%mod)*((ans+1)%mod)%mod;
    if (ret>=mod) ret-=mod;
    return ret;
}
int main ()
{
    scanf("%d",&T);
    while(T--)
    {
        scanf("%I64d",&n);
        LL l=n/2-100,r=n/2+100,mid,ans=0;
        while(l<=r)
        {
            mid=(l+r)>>1;
            if(sum(mid)<n) ans=mid,l=mid+1;
                        else r=mid-1;
        }
        printf("%I64d\n",(calc(ans,n)+1)%mod);
    }
    return 0;
}