考场上全写B题去了,没看这题的规律,不行啊我要多练找规律的题啊。
晚上看dls直播,除去第1项的1,从第2项开始,规律:1 3 5 7.....出现一次,2,6,10,14出现2次,4,12,20出现3次,其实这个数列在OEIS上是搜的出的,然而至今我还不太会看OEIS上的东西。。。。
那么由上面的规律,我们首先要知道比n小的那个完全出现完的数字是多少,dls说可以从n/2-100到n/2+100二分后再变求答案边求项数,最后输出那个正确记录的答案,这也是个规律吧,我没太想清楚,但是这样可以变成logn*log(200)的复杂度,就可以过了,然而第二天多校交流群里有位dalao丢出了一个getlast函数logn,我也想不太清楚,但就是能求出最后一个完全出现完的数字lastnum是多少。
求出lastnum之后,我们发现上面那些序列是等差的,而且出现1次的lowbit是1,出现2次的lowbit是2,出现3次的是4,那么我们只要求出lowbit相同的<=num的最后一个数字up[i]后等差数列求和就行了,我脑补出了一种做法,假设当前数字的二进制是1010011,那么up[0]=1010011,去掉那1位后num=1010010,up[1]=1010010,之后num=1010000,而up[2]就有个技巧了,up[2]=num-1-(mi[2]-1)=1001111-11=1001100,这就是小于lastnum的lowbit为4的最大数,因为对于第i位如果他在原数中是0的话,那么所求的up[i]必定是吧此时的lowbit(num)减掉,再把从第i位到lowbit-1位全部变成1,从第0到第i-1位全部变成0。
求出up[i]后,就枚举i然后等差数列求和了,一次处理复杂度logn
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define mod 1000000007
long long n,ans;
long long up[63];
long long mi[63],ni[63];
inline long long read()
{
long long x=0;char ch=getchar();
while(ch<'0' || ch>'9') ch=getchar();
while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
return x;
}
inline long long getlast()
{
long long tmp=0,num=0;
for(int i=62;i>0;i--)
if(tmp+(1LL<<i)-1<=n)
{
tmp+=(1LL<<i)-1;
num+=(1LL<<(i-1));
}
ans=((n-tmp)%mod)*((num+1)%mod)%mod;
return num;
}
inline void prework()
{
memset(up,0,sizeof(up));
n=read();n--;ans=0;
long long lastnum=getlast();
long long num=lastnum,num2=lastnum,lowbit,x,upnum;
int l;
for(int i=0;i<=61;i++)
{
x=mi[i];
if(x>num)
break;
if(num&x)
{
l=(int)(log2(1.0*x)+0.1);
if(num>up[l])
up[l]=num;
num^=x;
}
else
{
lowbit=num&-num;
upnum=num-1-(mi[i]-1);
l=(int)(log2(1.0*x)+0.1);
if(upnum>up[l])
up[l]=upnum;
}
}
}
inline void mainwork()
{
long long num,sum;
for(int i=0;i<=61;i++)
{
if(!up[i])
break;
num=((up[i]-mi[i])/mi[i+1]+1)%mod;
sum=( (((mi[i]+up[i])%mod) *num%mod)*ni[2])%mod;
ans=(ans+sum*(i+1))%mod;
}
}
inline void print()
{
ans=(ans+1)%mod;
printf("%lld\n",ans);
}
inline long long qp(long long a,long long b)
{
long long ans=1,cnt=a;
while(b)
{
if(b&1)
ans=(ans*cnt)%mod;
cnt=(cnt*cnt)%mod;
b>>=1;
}
return ans;
}
int main()
{
mi[0]=1;
for(int i=1;i<=61;i++)
mi[i]=mi[i-1]<<1;
ni[2]=qp(2,mod-2);
int t;
scanf("%d",&t);
for(int i=1;i<=t;++i)
{
prework();
mainwork();
print();
}
return 0;
}
今天发现机房某dalao有另一种规律,更加方便的写法
发现对于1,3,5,7,9出现1次,2,6,10,14出现2次这些数列,其实可以看做1,2,3,4,5,..lastnum这个数列
加2,4,6,8,10,..加4,8,12,...这个数列。吧后面的数列跟前面的合并得到这些数列。
那么就可以很快速的求出每个数列项数cnt,然后也是logn枚举每一个2次幂了。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define mod 1000000007
long long n,ans,lastnum;
long long up[63];
long long mi[63],ni[63];
inline long long read()
{
long long x=0;char ch=getchar();
while(ch<'0' || ch>'9') ch=getchar();
while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
return x;
}
inline long long getlast()
{
long long tmp=0,num=0;
for(int i=62;i>0;i--)
if(tmp+(1LL<<i)-1<=n)
{
tmp+=(1LL<<i)-1;
num+=(1LL<<(i-1));
}
ans=((n-tmp)%mod)*((num+1)%mod)%mod;
return num;
}
inline void prework()
{
// memset(up,0,sizeof(up));
n=read();n--;ans=0;
lastnum=getlast();
/* long long num=lastnum,num2=lastnum,lowbit,x,upnum;
int l;
for(int i=0;i<=61;i++)
{
x=mi[i];
if(x>num)
break;
if(num&x)
{
l=(int)(log2(1.0*x)+0.1);
if(num>up[l])
up[l]=num;
num^=x;
}
else
{
lowbit=num&-num;
upnum=num-1-(mi[i]-1);
l=(int)(log2(1.0*x)+0.1);
if(upnum>up[l])
up[l]=upnum;
}
}*/
}
inline void mainwork()
{
long long num,sum,cnt;
for(int i=0;i<=61;i++)
{
/*if(!up[i])
break;*/
if(mi[i]>lastnum)
break;
cnt=lastnum/mi[i];
up[i]=cnt*mi[i];
// num=((up[i]-mi[i])/mi[i+1]+1)%mod;
num=(mi[i]%mod+up[i]%mod)%mod;
sum=( (num*(cnt%mod)%mod) *ni[2])%mod;
// ans=(ans+sum*(i+1))%mod;
ans=(ans+sum)%mod;
}
}
inline void print()
{
ans=(ans+1)%mod;
printf("%lld\n",ans);
}
inline long long qp(long long a,long long b)
{
long long ans=1,cnt=a;
while(b)
{
if(b&1)
ans=(ans*cnt)%mod;
cnt=(cnt*cnt)%mod;
b>>=1;
}
return ans;
}
int main()
{
mi[0]=1;
for(int i=1;i<=61;i++)
mi[i]=mi[i-1]<<1;
ni[2]=qp(2,mod-2);
int t;
scanf("%d",&t);
for(int i=1;i<=t;++i)
{
prework();
mainwork();
print();
}
return 0;
}