[题解]ARC173A Neq Number
题意简述
如果一个数\(X\)满足其十进制表示中,相邻两位数不相同,则称\(X\)为\(Neq\)数。
\(T(1\leq T\leq 100)\)次询问第\(K(1 \leq K \leq 10^{12})\)个\(Neq\)数是多少。
思路
要想算出结果,我们首先要知道结果是多少位数,然后再一位一位填写。
例如,\(819\)和\(857\)所对应的答案位数分别是多少呢?
如表格所示,B列表示\(i\)位数中,\(Neq\)数有多少个。(如\(i=3\)则表示\(100∼999\)有多少\(Neq\))
这一列比较容易计算,因为第\(1\)位能填\(1∼9\)共\(9\)种数,第\(2\)位能填\(0∼9\)共\(10\)种,但是不能和第\(1\)位相等,所以也是\(9\)种……就这样,每一位都是\(9\)种填法,\(i\)位就是\(9^i\)种了。
而C列,则相当于B的前缀和,第\(i\)行表示\(1\)位、\(2\)位、……、\(i\)位数的\(Neq\)个数和。
我们可以用C列的数值来判断\(K\)的位数,找到第一个大于等于\(K\)的值,其位置就是\(K\)的位数。
按照此方法,\(K=819\),答案有\(3\)位;\(K=857\),答案有\(4\)位。
接下来从最高位开始填写。
拿\(K=280\)来举例,答案有\(3\)位。
- 为了计算第\(1\)位的值,我们依据前面的结论将“从第\(1\)位开始的第\(280\)个\(Neq\)数”转化为“从第\(100\)位开始的第\(190\)个\(Neq\)数”。
为什么要转换为\(100\)开始?因为前面的数不足\(3\)位,把它们去掉,只看\(3\)位的答案更容易进行接下来的步骤。 - 填写第\(1\)位:
- \(100∼199\)中有\(9^2=81\)个\(Neq\)数。
- \(200∼299\)中也有\(81\)个。
- 就这样,\(190\)个\(Neq\)数,能占满\(2\)个整百,所以第\(1\)位填\(3\),转化为从\(300\)开始找第\(190-162=28\)个\(Neq\)。
注意\(1\):这一位能放\(2\)个整百,这一位却填\(3\),是因为第\(1\)位不能填\(0\),后面有些情况这一位也需要\(+1\),后面再说。
注意\(2\):考虑另一个\(3\)位答案的\(K\),这一位同样能放\(2\)个整百,那么这一位一定填\(3\)吗?
并不是!如果这一位填满两个整百就不需要往下找了(即转化为了往下找\(0\)个),那么答案应当是\(200∼299\)的最后一个即\(298\),所以答案的百位不一定是\(3\)。
不能用简单除一下,那我们该怎么正确计算这一位应该填的数呢?
答案是向上取整\(-1\)(具体见代码)。
- 填写第\(2\)位:
- 同理,这一位能放\(3*9=27\)个整十。但这一位填\(4\),为什么呢?
这是因为上一位填的是\(3\),所以这一位必须越过\(3\),故答案\(+1\)。
思考:什么时候需要\(+1\),什么时候不需要呢?
① 第\(1\)位需要,因为从\(1\)开始填。
② 如果这一位要填的数\(\geq\)上一位也需要,因为要越过上一位填过的。
综上我们实现可以用一个\(last\)变量记下上一位选的,如果这一位\(\geq last\)则答案\(+1\)(而\(last\)变量可以赋初值为\(-1\),完美兼容①情况)。
转化为了从\(310\)开始找第\(28-27=1\)个\(Neq\)。
- 同理,这一位能放\(3*9=27\)个整十。但这一位填\(4\),为什么呢?
- 填写第\(3\)位:
- 这一位能放\(1*1=1\)个整一,这一位按照计算应该填\(0\),而上一位填的是\(4\)。所以上一位的\(4\)对我们没有影响,照常填\(0\)。
故答案\(=340\)。
时间复杂度\(O(T\ log_{10} K)\),比官方题解的二分快。
下面给出代码,有注释可结合理解:
Code
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int t;
long long k,cnt[20];
int main(){
cnt[0]=1;
for(int i=1;i<=15;i++) cnt[i]=cnt[i-1]*9;//相当于表格的B列
cin>>t;
while(t--){
cin>>k;
int sum=0,len;
for(len=1;;len++){//计算答案位数
if(sum+cnt[len]>=k) break;
k-=cnt[len];//转换k,同上面紫色步骤
}
int last=-1;//表示上一个放的是数字几,初值-1的原因蓝色部分
for(int i=len;i>=1;i--){//从最高位开始填
int num=(k+cnt[i-1]-1)/cnt[i-1]-1;
//即k/cnt[i-1]向上取整-1
//与红色部分对应
if(num<last){//小于则不受影响
cout<<num,last=num;
}else{//否则需要越过上一个数,答案+1
cout<<num+1,last=num+1;
}
k-=num*cnt[i-1];//再次转换k
}
cout<<endl;
}
return 0;
}
匆匆写完,感觉好多地方描述繁多却不清晰。
要是有任何不懂的地方或者建议,欢迎评论提出,谢谢!