咕咕东的奇妙序列

本学期第二次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;
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值