题解:P12191 [蓝桥杯 2025 省研究生组] 01 串
yanmingqian · · 题解
有被恶心到。但是其实不太难。
题意:将
我们先把第一位的
考虑暴力怎么做。我们可以枚举完整的数字,计算它们的二进制中 __builtin_popcount() 函数实现),最后单独计算剩下的那个不完整的数字。代码如下:
#include<iostream>
#include<cmath>
using namespace std;
int main(){
long long n,sum=0,ans=0,i=1; //sum用于记录当前枚举到了第几位,i为枚举到了第几个完整的数字,ans记录答案
cin>>n;
n--; //把第一位的0扔掉
while(sum<n){
sum+=floor(log2(i))+1;
ans+=__builtin_popcount(i);
i++;
}
i--;
long long x=sum-n;
cout<<ans-__builtin_popcount(i)+__builtin_popcount(i>>x);
return 0;
}
这样显然无法通过此题。分析代码,发现瓶颈在于枚举有多少个完整的数字和记录答案(也就是枚举计算代码中的 i 和 ans)。分别考虑如何优化。
先来考虑,如果我们已经知道了完整数字的个数,如何计算答案。也就是已知
1 2 4 5 7 9 12 13 15 17 20 22 25 28 32 33 ...
设这个序列的第
long long get(long long x){
if(!x){
return 0;
}
if(x&1){
return 2*get(x/2)+x/2+1;
}
else{
return 2*get(x/2)+x/2+1-__builtin_popcountll(x+1);
}
}
显然,由于每次
因此前面的暴力可以优化(实际上并没有,只是向正解贴近)为下面这样:
#include<iostream>
#include<cmath>
using namespace std;
long long get(long long x){
if(!x){
return 0;
}
if(x&1){
return 2*get(x/2)+x/2+1;
}
else{
return 2*get(x/2)+x/2+1-__builtin_popcountll(x+1);
}
}
int main(){
long long n,sum=0,ans=0,i=1;
cin>>n;
n--;
while(sum<n){
sum+=floor(log2(i))+1;
i++;
}
i--;
long long x=sum-n;
cout<<get(i)-__builtin_popcountll(i)+__builtin_popcountll(i>>x);
return 0;
}
虽然实际上复杂度还是不足以通过,但是我们的瓶颈少了一个。现在我们只需要设法快速求解完整的数字个数即可。
注意到在一段连续的区间内,所有数的二进制长度是相同的。这很好理解,与十进制类似,连续一段数字的位数是相同的。想办法利用这一点。考虑对原
综上,我们可以写出下面的 AC 代码:
#include<iostream>
#include<cmath>
using namespace std;
long long get(long long x){
if(!x){
return 0;
}
if(x&1){
return 2*get(x/2)+x/2+1;
}
else{
return 2*get(x/2)+x/2+1-__builtin_popcountll(x+1);
}
}
int main(){
long long n,sum=0,ans=0,i=1;
cin>>n;
n--;
//此处i枚举的是大段
while(sum<n){
sum+=i*(1ll<<(i-1));
i++;
}
i--;
sum-=i*(1ll<<(i-1));
i--;
long long x=0; //通过大段数量统计数字个数
for(long long j=0;j<i;j++){
x+=(1ll<<j);
}
sum=n-sum;
i++;
ans=get(x+sum/i);
x+=sum/i+1;
long long r=i-sum%i;
cout<<ans+__builtin_popcountll(x>>r);
return 0;
}
时间复杂度约为