WEEK9 周记 限时赛——模拟题_咕咕东的奇妙序列

WEEK9 周记 限时赛——模拟题_咕咕东的奇妙序列

一、题意

1.简述

一个奇怪的无限序列:112123123412345 …这个序列由连续正整数组成的若干部分构成,其 中第一部分包含1至1之间的所有数字,第二部分包含1至2之间的所有数字,第三部分包含1至3之间的所 有数字,第i部分总是包含1至i之间的所有数字。所以,这个序列的前56项会是 11212312341234512345612345671234567812345678912345678910,其中第1项是1,第3项是2,第20项是 5,第38项是2,第56项是0。
现在想知道第 k 项数字是多少!

2.输入格式

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

3.输出格式

输出包含q行
第i行输出对询问 的输出结果。

4.样例

Input

5 1 3 20 38 56

Output

1 2 5 2 0

Hint
数据点q(上限)k(上限)
1,2,350055
4,5,6100 1 0 6 10^6 106
7,8,9,10500 1 0 8 10^8 108

二、算法

主要思路

这个题暴力的思想能够骗分,对于时间不够时这样干是聪明的。不过要想AC暴力的方法是行不同的。


这个题要充分挖掘和利用等差数列的性质。
针对下面这个序列讲解算法:
1121231234123451234561234567123456781234567891234567891012345678910111234567891011121234567891011121312345678910111213141512345678910111213141516
假设要找第70个数字,序列中已标红。
首先确定这个数字位于哪一种递增序列(这里以递增序列最后一个数是几位数来划分)。
所以我们需要先预处理一些信息。

//setLth存储递增序列最后一个数是i位数的集合中,共有多少个递增序列,由于k的数据量限制,我们只需要知道i<=9的setLth即可
//l_s[i]存储该集合中的第一个递增序列的数字个数,r_s[i]存储该集合中最后一个递增序列的数字个数
//需要注意的是,在同一集合中,两个递增序列的数字个数相差i个(即最后一个元素的位数)
//前一个集合i-1的最后一个递增序列的位数等于后一个集合i的第一个递增序列的位数-i。
//sum[i]算出这个集合i共有多少个数字(同一集合不同递增序列的数字个数是成等差数列增长的,所以总的位数可以利用等差求和公式来算)
setLth[1] = 9;    
l_s[1] = 1;
r_s[1] = 9;
sum[1] = cul(1,9,9);
for(i=2;i<=10;i++){
	setLth[i] = setLth[i-1]*10;
	l_s[i] = r_s[i-1] + i;
	r_s[i] = r_s[i-1] + setLth[i]*i; 
	sum[i] = cul(l_s[i],r_s[i],setLth[i]);   //cul是等差数列求和公式的实现
}

通过下面的代码确定k是属于哪个集合。

for(i=1;i<=10;i++){//确定k属于哪一个集合段(最大数位数相同为一个集合,一个集合中的元素之间的等差为位数) 
	if(k>sum[i]) k -= sum[i];
	else break;
}//出循环之后k是属于最大数位数为i的集合段

然后我们就可以确定k=70是属于递增序列最后一个数字位数为2的集合。之后我们需要确定k是在这个集合中的哪一个递增序列,即确定所属的递增序列的最后一个元素是多少。这里由于setLth[i]可能比较大所以用二分来遍历。

ll l = 1,r = setLth[i],mid,num; 
while(l<=r){//二分确定k属于该集合段中的哪一个递增段 
	mid = (l+r)/2;   //mid是第几个递增序列
	num = cul(l_s[i],r_s[i-1]+mid*i,mid);   
	    //num等于这个集合中到mid这个递增序列的最后一个元素,一共有多少个数字,或者说最后一个元素在集合中的位置是多少。
	if(k>num) l = mid+1;
	else r = mid-1;
}
num = cul(l_s[i],r_s[i-1]+r*i,r);
k -= num;  //更新k在递增序列中的位置

然后我们就可以确定k=70是属于递增序列:123456789101112。之后我们再确定k属于这个序列中的哪一种数的一部分(这里按照数的位数划分数字)。

for(i=1;k>r_s[i];i++){}//确定k是在几位数中,得到的i就是位数 
		k -= r_s[i-1];    //r_s数组还能接着用

因此我们确定k=70位于一个2位数中。之后我们再确定k是属于哪一个数的哪一位并最终确定这个数是什么。

ll pos = (k-1)/i+pow(10,i-1);//确定k在哪个数中
int wei = i-((k-1)%i);//确定了k在pos中是第几位 
int number = pos/(int)pow(10,wei-1) % 10;//确定这一位上是什么数字
	//需要强制类型转换,因为pow返回double 

三、代码

#include<iostream>
#include<cstring>
#include<string>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<queue> 
typedef long long ll;
ll sum[20],l_s[20],r_s[20],setLth[20];
ll cul(ll a,ll b, ll lth){
	return ((a+b)*lth)/2;
}
//应该先除再乘,这样能防止超出longlong,但这样的话就应该判断奇偶。 
using namespace std;
int main(){
	int i;
	setLth[1] = 9;
	l_s[1] = 1;
	r_s[1] = 9;
	sum[1] = cul(1,9,9);
	for(i=2;i<=10;i++){
		setLth[i] = setLth[i-1]*10;
		l_s[i] = r_s[i-1] + i;
		r_s[i] = r_s[i-1] + setLth[i]*i; 
		sum[i] = cul(l_s[i],r_s[i],setLth[i]); 
	}
	int q;
	scanf("%d",&q);
	for(int tt=0;tt<q;tt++){
		long long k;
		scanf("%lld",&k);
		for(i=1;i<=10;i++){//确定k属于哪一个集合段(最大数位数相同为一个集合,一个集合中的元素之间的等差为位数) 
			if(k>sum[i]) k -= sum[i];
			else break;
		}//出循环之后k是属于最大数位数为i的集合段 
		
		ll l = 1,r = setLth[i],mid,num; 
		while(l<=r){//二分确定k属于该集合段中的哪一个递增段 
			mid = (l+r)/2;
			num = cul(l_s[i],r_s[i-1]+mid*i,mid);
			if(k>num) l = mid+1;
			else r = mid-1;
		}
		num = cul(l_s[i],r_s[i-1]+r*i,r);
		k -= num;
		
		//前面的i没有用了 
		for(i=1;k>r_s[i];i++){}//确定k是在几位数中,得到的i就是位数 
		k -= r_s[i-1]; 
		
		ll pos = (k-1)/i+pow(10,i-1);//确定k在哪个数中
		
		int wei = i-((k-1)%i);//确定了k在pos中是第几位 
		int number = pos/(int)pow(10,wei-1) % 10;//确定这一位上是什么数字
			//需要强制类型转换,因为pow返回double 
		printf("%d\n",number); 
	} 
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值