题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6304
题解:
参考链接:https://blog.csdn.net/qq_36258516/article/details/81185932
https://blog.csdn.net/luyehao1/article/details/81184708
我们先打个表,
出现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的等差数列
[1,n] 出现的次数以此为 1 2 1 3 1 2 1 4 1 2 1 3 1 2 1 5 ... 发现1...(2^(i-1)-1)出现的次数=2^(i-1)+1...(2^i-1)出现的次数,而2^(i-1)出现的次数+1=2^i出现的次数。由此可以推得,前2^i个数出现的次数和=前2^(i-1)个数出现的次数和*2+1。
然后,我们先求出x,x表示当a[n]及以前出现的且已经完全结束的最大整数(ex. 若a[n]=8,但a[n+1]=8,则x=7)。
我们可以发现,数字num出现的次数是其二进制末尾0的个数+1。
这题的规律真。。。。TM难弄。
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long LL;
const LL mod=1000000007;
LL inv=(mod+1)/2;
LL ans[110],num[110];
LL solve(LL x)
{
LL sum=0;
for(LL i=1;i<=x;i*=2) ///等差数列求和,每个i代表首项
{
LL item=(x-i)/(i*2);
LL last=i+item*i*2;
item=(item+1)%mod;
last=((i+last)%mod*item)%mod*inv%mod;
last=last*(__builtin_ctz(i)+1)%mod;
sum=(sum+last)%mod;
}
return (sum+1)%mod;
}
int main()
{
ans[0]=num[0]=1;
for(int i=1;i<=63;i++)
ans[i]=ans[i-1]*2+1,num[i]=num[i-1]*2;
///预处理前num[i]数字中,出现的次数为ans[i]
int ncase;
scanf("%d",&ncase);
while(ncase--)
{
LL n;
scanf("%lld",&n);
n--;///因为我们是从第二个当成第一个开始的(1) 1 2 1 3 1 2 1 4
if(n==0){
puts("1");continue;
}
LL item=n,sum=0;
for(int i=62;i>=0;i--){
if(item>=ans[i]){
item-=ans[i];
sum+=num[i];///表示前num[i]个数中,出现了ans[i]次
}
}
// printf("item=%lld,sum=%lld",item,sum);
LL t=solve(sum);
t=(t+item*(sum+1))%mod;
printf("%lld\n",t);
}
return 0;
}