本学期第二次csp模拟又磕在了C题orz,
(虽然说通过数据范围骗了点分),无奈在看了大佬的思路之后,才明白这道题考查的是二分,下面就分享一下我的理解。
问题描述:
咕咕东睡梦中突然想到了一个奇怪的无限序列:112123123412345…
这个序列由连续正整数组成的若干部分构成,其中第一部分包含1至1之间的所有数字,第二部分包含1至2之间的所有数字,第三部分包含1至3之间的所有数字,第i部分总是包含1至i之间的所有数字。所以,这个序列的前56项会是11212312341234512345612345671234567812345678912345678910,其中第1项是1,第3项是2,第20项是5,第38项是2,第56项是0。
咕咕东 现在想知道第 k 项数字是多少!但是她睡醒之后发现老师讲的东西已经听不懂了,因此她把这个任务交给了你。
input:
输入由多行组成。
第一行一个整数q表示有q组询问(1<=q<=500)
接下来第i+1行表示第i个输入ki,表示询问第k项数字。(1≤ki≤1e18)
output:
输出包含q行
第i行输出对询问ki的输出结果。
样例输入:
5
1
3
20
28
56
样例输出:
1
2
5
2
0
问题思路:
对于这个题,由于是第i部分包含第1-i之间的所有数字,那我们可以对这些数字进行分组,首先是i为1位数的为一组,i为2位数的为一组,由此类推。并且每组的个数我们可以通过一个梯形公式来计算。梯形的上底为第i组的第一个数所对应的数列长度,可以通过上一组的最后一个数加上这一组数字的位数来得到。在求梯形的下底时,由于每一行都是一个等差序列,即公差为该组的数字位数,所以可以通过上底加上改组的层数,也就是梯形的高乘该组的数字位数来得到。在计算完该组的数字个数之后,与前几组的数字个数进行相加,就能得到从该组之前的所有数字的个数。并其还有一个额外的数组来记录每一行的分组。每一组的高是10倍的关系。由于k不大于1e18,那么我们可以判断所有组的数字个数,如果大于1e18,就可以退出了。
当我们输入一个数k时,我们首先可以找到它属于哪个组,可以通过第i组及其之前的数字个数进行判断,如果它属于第i组,那么将k减去第i-1组及之前的数字个数,即k只可能出现在该组中。在我们找到k所属的组之后,可以通过二分法,找到k所属于该组的那一层。即不断二分该组梯形的高度,如果k大于二分之后的上半部分,那么k就出现在该梯形的下半部分,那么就更新left,否则更新right,重复此过程,然后就能找到k所属于哪一层。然后在该层里,同样有一个分组,和以上的方式类似,同样找到k属于该层中的哪一组,然后用k减去该组之前的数字。
在第i组中,所有的数都是i位数,所以我们可以通过k/i来得到是第几个i位数,同样有一步求余操作,来判断是该数的第几位,最后通过计算得到真值,然后求取某一位即可。
代码:
#include<iostream>
#include<cmath>
using namespace std;
long long int lo[50];//下底
long long int hi[50];//上底
long long int all[50];//第i组及之前的所有数字个数
long long int hang[50];//在每一行的分组
long long int hh=9;//梯形的高
long long int ku,q,k;//ku为一共分了多少的组
int main()
{
lo[0]=hi[0]=hang[0]=all[0]=0;//初始化
for(long long int i=1;;i++)
{
hi[i]=lo[i-1]+i;//新组的上底为上一组的下底加上该组的数的位数
lo[i]=hi[i]+i*(hh-1);//下底通过等差来计算
hang[i]=hang[i-1]+i*hh;//每一行的分组,第i组的个数等于前几组加该组的数字位数乘个数
all[i]=all[i-1]+((hi[i]+lo[i])*hh)/2;//第i组的所有数字个数
ku=i;//组数
if(all[i]>=pow(10,18))//判断退出条件
break;
hh=hh*10;//高扩大十倍
}
cin>>q;
for(long long int m=0;m<q;m++)
{
cin>>k;
for(long long int i=1;i<=ku;i++)
{//找到第一个数字个数大于k的组
if(k<=all[i])
{
k=k-all[i-1];//将前一组及之前的数字个数从k中剪掉
long long int he=9*i*10;//该组的高
long long int le,ri;
le=1;
ri=hh;
while(le+1<ri)//二分高
{
long long int mid=(le+ri)/2;
//上半部分梯形的数字个数
long long int up=((hi[i]+hi[i]+i*(mid-1))*mid)/2;
if(up>k)//如果大于k,这在上半部分,否则在下半部分
ri=mid;
else
le=mid;
}
long long int thele=0;
if((((hi[i]+hi[i]+i*(le-1))*le)/2)<k)//在出循环后还要具判断一次在哪一层
thele=ri;
else
thele=le;
//将该层之前的数字减去
k=k-((hi[i]+hi[i]+i*(thele-2))*(thele-1))/2;
for(long long int j=1;j<=i;j++)
{//找到该层中第一个数字个数大于k的组
if(k<=hang[j])
{
k=k-hang[j-1];//将之前的组的数字个数减去
long long int num1;
if(k%j==0)//如果k的数恰好为该组的数的位数
{
num1=j;//那么他就是该组中某一个数的最低位
k=k/j;//k赋值为它是第几个数
}
else
{
num1=k%j;//否则是某一个数的从高向低数第k%j位
k=k/j+1;//k在第k/j+1个数中
}
long long int truenum;
truenum=k+pow(10,j-1)-1;//计算该数的真实值
int wei=(int)log10(truenum)+1-num1;//前半部分计算出了该数共有多少位,减去num1表示需要移位几次
for(int t=0;t<wei;t++)
{
truenum=truenum/10;
}
int temp=truenum%10;//将移位后的数取低位即为答案
cout<<temp<<endl;
break;
}
}
break;
}
}
}
return 0;
}