【题解】数 [Bzoj5282]
【题目描述】
众所周知,萌萌哒六花不擅长数学,所以勇太给了她一些数学问题做练习,其中有一道是这样的:
一个正整数被称为是好的当且仅当它的十进制表示是其二进制表示的后缀。
例如正整数 ((100)_{10}) 就是一个好的正整数,它的二进制表示是 ((1100100)_{2}),而正整数 ((2)_{10})
就不是一个好的正整数,它的二进制表示是 ((10)_{2})。
现在勇太想要知道第 (K) 小的好的正整数。
当然,这个问题对于萌萌哒六花来说实在是太难了,你可以帮帮她吗?
【分析】
又是一道 ( ext{jiry_2}) 搞出来报复社会的毒瘤题。。。
萌萌哒六花好评。
考试时瞎 (jb) 枚举搞到了 (30pts),然后就不会了。
考虑在一个 (k) 位十进制数 ((x)_{10}) 的最前面加一个 (1) (即第 (k+1) 位)所产生的变化,对于数值大小来说增加了 (10^k),而这东西一定是 (2^k) 的倍数,所以在不考虑进位的情况下 ((x)_{2}) 的第 (k+1) 位数值会增加 (5^k),考虑进位后可以发现对 ((x)_{2}) 的后 (k) 位始终没有影响,于是暴力做法就出来了:
从小到大枚举 (k),用一个集合 (Q) 维护当前所有的合法数字,每次 (k) 增大后尝试在这些数的前面加 (1/0)(加 (0) 就是不变),并将合法的加入新集合,不合法的舍去。当求出第 (K) 个合法数字时就直接输出。
发现这玩意儿要写高精,但复杂度双双爆炸,按照吉司机的神仙思路,把大数压成 ((bit=2^{base})) 进制(标程里面 (base=25)),复杂度直接降到 (O(frac{KL}{base}))((L) 为数字长度)。
(说实话这个复杂度的分析我是真没看懂,总之能过就行了...)
【Code】
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<vector>
#define LL long long
#define Re register int
using namespace std;
const int N=5e5+3;
const int bit=(1<<25),base=25;//bit:高精度大数的进制
struct Int{
int l,a[150];
Int(Re O=0){memset(a,0,sizeof(a)),a[l=1]=O;}
inline void operator=(const int O){*this=Int(O);}
inline void operator+=(const Int &O){//高精加高精
l=max(l,O.l);
for(Re i=1;i<=l;++i)a[i]+=O.a[i];l+=2;
for(Re i=1;i<l;++i)if(a[i]>=bit)
a[i+1]+=(a[i]>>base),a[i]&=(bit-1);//a[i+1]+=a[i]/bit,a[i]%=bit;
while(l>1&&!a[l])--l;//去掉前导0
}
inline void operator*=(const int O){//高精乘低精
for(Re i=1;i<=l;++i)a[i]*=O;l+=2;
for(Re i=1;i<l;++i)if(a[i]>=bit)
a[i+1]+=(a[i]>>base),a[i]&=(bit-1);//a[i+1]+=a[i]/bit,a[i]%=bit;
while(l>1&&!a[l])--l;//去掉前导0
}
inline void print(){//转为十进制数输出
vector<int>ans;
while(l&&a[l]){
Re yu=0;
for(Re i=l,x;i>=1;--i)//高精除低精,即a/=10
x=a[i],a[i]=((yu<<base)+x)/10,yu=((yu<<base)+x)%10;//即a[i]=(yu*bit+x)/10,yu=(yu*bit+x)%10
ans.push_back(yu);//算出来的余数(a%10)就是十进制下的当前位的值
while(l>1&&!a[l])--l;//去掉前导0
}
for(Re i=ans.size()-1;i>=0;--i)printf("%d",ans[i]);//获取值是从低位到高位,所以要倒叙输出
}
inline int judge(Re j){return a[j/base+1]&(1<<j%base);}//判断二进制下第j位的数值是否为1
}B,Q[N];
int K,O,fa[N];
inline int find(Re x){return x==fa[x]?x:fa[x]=find(fa[x]);}
int main(){
// freopen("number.in","r",stdin);
// freopen("number.out","w",stdout);
scanf("%d",&K),Q[fa[1]=O=1]=0,B=1;//先初始化一个0
for(Re k=0;;++k){
Re ed=find(O),pre=0;
for(Re i=find(1);pre!=ed;pre=i,i=find(i+1)){
Int tmp=Q[i];tmp+=B;//B: 10^k
if(tmp.judge(k)){//十进制下第k位加1的情况,如果加上1*10^k之后二进制第k位为1则可以加入该点
Q[++O]=tmp,fa[O]=O;
if(!--K){tmp.print();return 0;}
}
if(Q[i].judge(k))fa[i]=find(i+1);//十进制下第k位加0的情况,如果二进制第k位为1则需要删掉该点
}
B*=10;
}
fclose(stdin);
fclose(stdout);
return 0;
}