程序设计M2补题——C-咕咕东的奇妙序列

题目描述

有一个无限序列,它由若干个部分组成,其中第一部分包含1至1之间的所有数字,第二部分包含1至2之间的所有数字,第三部分包含1至3之间的所有数字,第i部分总是包含1至i之间的所有数字。求第k项数字是多少?(一位数字算一项)

Input

输入由多行组成。
第一行一个整数q表示有q组询问
接下来第i+1行表示第i个输入ki,表示询问第ki项数字

Output

输出包含q行
第i行输出对询问ki的输出结果。
在这里插入图片描述

解题思路

首先来看这个序列,将这个序列整理成这种形式:
1
1 2
1 2 3
……
1 2 3 …… 9
1 2 3 …… 9 1 0
……
其中每一行就是题目说的每一部分,而每一行的数字个数是有规律的,对于第n行,设s[n]是该行的项数

  • 如果n是1位数,则s[n]=s[1]+(n-1);
  • 如果n是2位数,则s[n]=s[10]+2*(n-10);
  • 如果n是k位数,则s[n]=s[10(k-1)]+k*(n-10(k-1)];

其中s[1],s[10]等的关系则满足:

  • s[1]=1;
  • s[10]=s[9]+2;
  • ……
  • s[10(k-1)]=s[10(k-1)-1]+k;

由此可以发现,这个序列每一行的项数,以每一行行序号的位数公差,构成了不同的等差数列,因此求项数就可以转换为多个等差数列求和,反之,当我们知道某个项时,我们可以利用二分的方法来反推出该项在那一部分(哪一行),到此,完成了第一步。

然后我们观察对于某一行的数字,它也可以分为不同部分:

  • 前9个数,每个数就是一项;
  • 第10到第99个数,每个数占两项;

也就是说根据数的位数不同,依然可以将每一行的数划分为不同部分,k位数属于第k部分,所有第k部分的数字,每个数字占k项,每个部分的项数,依然是以k为公差的等差数列,也即是说,对于给定的一行,我可以求得某个数字在该行的第几项,反过来,如果我知道某个数字在第几项,那我也可以利用二分反推出这个数字是几,到此,完成了第二步;

然后对于某一个数字,它是几位数,就占几项,如果我们知道一个数在这个数的第几项,那我们就能反推出这个数是几(求模运算),至此,所有的任务就完成了,整个程序的过程可以分为以下几步:

  • 对于给定的第k项,二分求解出第k项在第几个部分(第几行),假设为第n行;
  • 此时可以解得第k项在第n行的第几项,假设为第i项;
  • 对于第n行,可以再次二分求解出,第i项对应哪个数字,假设为p;
  • 此时可以解得第k项在数字p的哪一位,然后利用取模运算,找到这一位的数字输出即可。

实现代码

#include<iostream>
#include<cstring>
#include<cmath> 
using namespace std;

long long ans[20];

long long find_num(long long k)
{ 
 	long long d=1,a=1,ans=0,num=1,n;
	while(k>=10*num)
	{
		num*=10;
		n=num-num/10;
		ans+=a*n+n*(n-1)*d/2;
		d++;
		a+=(n-1)*(d-1)+d;
	}
	n=k-num+1;
	ans+=a*n+n*(n-1)*d/2;
	return ans;
}

long long find_num2(long long k)
{
	long long cnt=1,a=0,n=1;
	while(k>=10*n)
	{
		a+=9*n*cnt;
		cnt++;
		n*=10;
	}
	a+=cnt*(k-n+1);
	return a;
}

int main()
{
	int q;
	scanf("%d",&q);
	//cin>>q;
	for(int i=0;i<q;++i)
	{
		long long k;
		scanf("%lld",&k);
		long long l=0,r=1e9,n,mid;
		long long s;
		while(l<=r)
		{
			mid=(l+r)/2;
			s=find_num(mid);
			if(s<k)
			{
				n=mid;
				l=mid+1;
			}
			else
				r=mid-1;
		}
		k-=find_num(n);
		l=0;
		r=n+1;
		while(l<=r)
		{
			mid=(l+r)/2;
			s=find_num2(mid);
			if(s<k)
			{
				n=mid;
				l=mid+1;
		    }
			else
			    r=mid-1;
		}
		k-=find_num2(n);
		n++;
		int j=0;
		while(n>0)
		{
			ans[j]=n%10;
			j++;
			n/=10;
		}
		printf("%d\n",ans[j-k]);
		memset(ans,0,sizeof(ans));
	}
	return 0;
}

总结

这道题看似是一道二分的题目,其实是一道数学题,一道找规律的题目,只要能找到第n行的项数与n的位数的关系,就很好解决了。
另外,这道题数据卡的很极限,所以不能用pow,虽然我在本地测试的数据,都对了(构造的各种极限点都没问题),但评测机还是WA了,因为会有精度丢失,这是我之前怎么都没想到的,奇怪的debug知识增加了!(深夜连WA13发)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值