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