HDOJ2018多校第一场补题1007 Chiaki Sequence Revisted

emmmmmm

事先声明下,这题不是我补的,毕竟对于数学八字不合,是我队友补的,然后,由于B站优良的学习氛围,他选择了在B站上发,我就当个板砖工了~

B站链接:https://www.bilibili.com/read/cv795958

题意是,给了一个数列的递推公式,让我们求前缀和Sn,一共有T组case,Sn可能很大,答案对10^9+7取余数,n为 <= 10^18的正整数。T <= 10^5

 

拿到这题咱也很蒙圈(当然考试的时候没做出来啦)。找规律的题目真的很吃智商……

 

根据赛后的大佬吹水直播给的提示,对于该数列中的一个项An,这一项的数字在数列中将会出现的次数是lowbit(An)的二进制位数,即An中含有因子2的个数再+1。

lowbit(x)=x&(-x);

这样数列中出现1次的数会是1,3,5,7,……

出现2次的数将会是2,6,10,14…… (即1,3,5,7……每项×2^1)

出现3次的数将会是4,12,20,28…… (即1,3,5,7……每项×2^2)

……以此类推……

N An

1   1

2   1

3   2

4   2

5   3

6   4

7   4

8   4

9   5

10 6

11 6

12 7

13 8

14 8

15 8

16 8

17 9

18 10

19 10

20 11

21 12

22 12

23 12

24 13

25 14

但是考虑到我们可能不会取到完整的数列,可能对于某一项的数字y,在截取的数列中出现的次数小于lowbit(y)的二进制位数,又观察可知数列是单调不下降的,所以少出现的项一定和最后出现的数字相同,只需要把这一种数字缺失的次数补回来就行了。

接下来的问题就是,怎么找到这样的位置,使得在该位置之前(包括它自身这个位置在内)所有的数字都出现了我们预计出现的次数。

记X为这个位置上的数字。

出现1次的数会是1,3,5,7,…… 这些数字对应1~X中奇数的个数

出现2次的数将会是2,6,10,14…… 这些数字对应1~[X÷2]中奇数的个数

出现3次的数将会是4,12,20,28…… 这些数字对应1~[X÷4]中奇数的个数

……以此类推……

那么最后一次出现数字X对应的项的下标为

n0=[(X+1)/2]*1+[([X/2]+1)/2]*2+[([X/4]+1)/2]*3+[([X/8]+1)/2]*4+[([X/16]+1)/2]*5+……

[]为下取整

观察到当X为2的幂的时候,对应的下标为2X,

所以可以大胆估计这个X会在n÷2附近取到。

数据可能取到10^18,这当然要用二分搜索,但是裸的二分依然会超时。根据大佬的提示,把X的二分区域缩小到(n÷2)±100就可以了。

最后要统计前缀和Sn,我们这样计算:

记前面完整取到的项到下标n0为止,n0在上面已经给出表达式。

记出现i次的数的个数为Xi,容易得到Xi为[([X/(2^(i-1))]+1)/2]

[]为下取整

出现1次的数会是1,3,5,7,…… 这部分和为 1*(2^0)*(X1)^2

出现2次的数将会是2,6,10,14…… 这部分和为 2*(2^1)*(X2)^2

出现3次的数将会是4,12,20,28…… 这部分和为 3*(2^2)*(X3)^2

……以此类推……

然后是把不足的部分加上去,这部分为 (n-n0)*(X+1)

注意可能中间步骤超过long long范围,要及时取模。

最后,附上队友优美的代码

#include<bits/stdc++.h>
using namespace std;
const long long M=1000000007;
long long lowbit_length(long long x)
{
    long long ret=1;
    while(!x&1)
    {
        ret++;
        x>>=1;
    }
    return ret;
}
long long findnum(long long x)
{
    long long ret=1,i=1;
    while(x)
    {
        ret+=(i*(long long)((x+1)>>1));
        x>>=1;i++;
    }
    return ret;
}
long long mul(long long a,long long b,long long c)
{
    a%=M;b%=M;c%=M;
    return (((a*b)%M)*c)%M;
}
long long findx(long long n)
{
    long long left=(n>>1)-100,right=(n>>1)+101,mid;
    if(left<=0)left=1;
    while(left<right)
    {
        mid=(left+right+1)>>1;
        if(findnum(mid)>n)right=mid-1;
        else left=mid;
    }
    return left;
}
int main()
{
    long long T,n,x,n0,x0,ans,tmp,tmp2;
    scanf("%lld",&T);
    while(T--)
    {
        scanf("%lld",&n);
        if(n==1){cout<<"1\n";continue;}
        x=findx(n);
        n0=findnum(x);
        x0=x;
        ans=(1+mul(n-n0,x+1,1))%M;
        tmp2=1;
        for(int i=1;x0>0;i++)
        {
            tmp=(x0+1)>>1;
            tmp=mul(tmp,tmp,1);
            ans=(ans+mul(tmp,i,tmp2))%M;
            tmp2=(tmp2<<1)%M;
            x0>>=1;
        }
        cout<<ans<<'\n';
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/AC_hunter/article/details/81194766