題目連結:Sprout Online Judge No. 840
題目敘述
你有一個大整數
也就是說,這個大整數是把所有非負整數依序串在一起。
現在,給你 、
,請你輸出,在這個大整數中,由左至右第
次出現數字
,是出現在第幾個位置?
位置從 1 開始算,並保證 是介於
到
之間的整數。
注意到,因為這個大整數太特別了,你定義這個大整數的第一位是 。
^
,
想法
數字這麼大,一定是二分搜尋,找出最後一個含有數字 小於
的數字,而求出在
之前有多少數字
的方法如下。
若 ,則要分開討論。
1.
我們逐位討論,每一次將數字分為 ,分別是現在這一位數,在他左邊的數,以及在他右邊的數。
以 為例:
第一次進入迴圈時 。
因為 ,因此會有
,共
個
。
第二次進入迴圈時 。
同樣因 ,因此會有
,共
個
。
第三次進入迴圈時 。
這次 ,因此
,共
個
。
最後不要忘記還有 本身也是一個。
2.
同樣逐位討論,每一次將數字分為 ,分別是現在這一位數,在他左邊的數,以及在他右邊的數。
以 為例:
第一次進入迴圈時 。
因為 ,因此會有
,共
個
。
第二次進入迴圈時 。
同樣因 ,因此會有
,共
個
。
第三次進入迴圈時 。
這次 <
,因此
,共
個
。
※上面的說明,也有 HackMD 版,有興趣的伙伴,可以點按連結:求 0~n 當中有幾個數字 (k(0 \le k \le 9)) – HackMD(↗)。
實作
#include<bits/stdc++.h>
using namespace std;
using ll=long long;
// just for 0
ll countDigit(ll n){
if(n<1) return 1;
ll high=n,tp=0,cur=0,low=0,base=1,ret=0;
while(high>0){
high=n/(10*base);
tp=n%(10*base);
cur=tp/base;
low=tp%base;
if(cur==0){
ret+=(high-1)*base+low+1;
}else{
ret+=high*base;
}
base*=10;
}
return ret+1;
}
// only for 1~9
ll countDigit(ll n,ll k){
if(k==0) return countDigit(n);
ll high=n,tp=0,cur=0,low=0,base=1,ret=0;
while(high>0){
high=n/(10*base);
tp=n%(10*base);
cur=tp/base;
low=tp%base;
if(cur==k){
ret+=high*base+low+1;
}else if(cur<k){
ret+=high*base;
}else{
ret+=(high+1)*base;
}
base*=10;
}
return ret;
}
int main(){
ios::sync_with_stdio(0);cin.tie(0);
ll n,k;
cin>>n>>k;
ll l=0,r=1000000000000000000;
while(r-l>1){
ll mid=l+r>>1;
if(countDigit(mid,k)>=n){
r=mid;
}else{
l=mid;
}
}
ll ans=countDigit(l);
for(int i=1;i<=9;++i){
ans+=countDigit(l,i);
}
ll ctd=countDigit(l,k);
l++;
vector<int> ns;
while(l>0){
ns.emplace_back(l%10);
l/=10;
}
reverse(ns.begin(),ns.end());
// search the nth k
for(int i=0;i<ns.size();++i){
if(ctd==n) break;
if(ns[i]==k) ctd++;
ans++;
}
cout<<ans<<"\n";
}