Week8 - 程序设计思维与实践 - 咕咕东的奇妙序列(第二次CSP模拟T4)

OriginE2. Numerical Sequence (hard version)

题目描述

咕咕东 正在上可怕的复变函数,但对于稳拿A Plus的 咕咕东 来说,她早已不再听课。

此时她在睡梦中突然想到了一个奇怪的无限序列:112123123412345......

这个序列由连续正整数组成的若干部分构成,其中第一部分包含1至1之间的所有数字,第二部分包含1至2之间的所有数字,第三部分包含1至3之间的所有数字,第i部分总是包含1至i之间的所有数字。

所以,这个序列的前56项会是11212312341234512345612345671234567812345678912345678910,其中第1项是1,第3项是2,第20项是5,第38项是2,第56项是0。

咕咕东 现在想知道第 k 项数字是多少!但是她睡醒之后发现老师讲的东西已经听不懂了,因此她把这个任务交给了你。

输入格式

输入由多行组成。

第一行一个整数q表示有q组询问(1<=q<=500)(1<=q<=500)(1<=q<=500)

接下来第 i+1行表示第i个输入 k i k_i ki,表示询问第 k i k_i ki 项数字。 ( 1 < = k i < = 1 0 18 ) (1<=k_i<=10^{18}) (1<=ki<=1018)

输出格式

输出包含 q 行

第i行输出对询问 k i k_i ki 的输出结果。

样例输入

5
1
3
20
38
56

样例输出

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

思路

致命套娃题,当场看到这题…(一层一层往下搜好恶心 ),思路是先定界(定到 k 属于几位数)然后再二分查找(但当时脑子有点混一直在纠结上界取多少还有怎么搜),混着推下去又突然发现把位数之和与数值之和给搞混了,这道题的细节有点多,看到时间不够,无奈改暴力打表水了6个点(其实想想,如果在另一个程序里先打表打到 1 0 18 10^{18} 1018,然后直接赋值,是不是这道题能多偷点分呢2333),下面梳理一下这道题的思路:

  • 由等差数列求和公式确定位数边界(即确定第k项属于几位数)

    • a [ i ] a[i] a[i] 表示 i i i位数的上界, b [ i ] b[i] b[i]表示最后一个 i i i位数所对应的序列长度, c o u n t count count表示 i i i 位数序列的个数, i i i 表示当前位数
    • 1位数: a [ 1 ] = ( 1 + 9 ) × 9 / 2 a[1]= (1+9)×9/2 a[1]=(1+9)×9/2
    • 2位数: a [ 2 ] = [ ( 9 + 2 ) + ( 9 + 2 × 90 ) ] × 90 / 2 a[2]=[(9+2)+(9+2×90)]×90/2 a[2]=[(9+2)+(9+2×90)]×90/2
    • 3位数: a [ 3 ] = [ ( 9 + 2 × 90 + 3 ) + ( 9 + 2 × 90 + 3 × 900 ) ] × 900 / 2 a[3]=[(9+2×90+3)+(9+2×90+3×900)]×900/2 a[3]=[(9+2×90+3)+(9+2×90+3×900)]×900/2
    • i 位数: a [ i ] = [ ( b [ i − 1 ] + i ) + ( b [ i − 1 ] + c o u n t × i ) ] × c o u n t / 2 a[i]=[(b[i-1]+i)+(b[i-1]+count×i)]×count/2 a[i]=[(b[i1]+i)+(b[i1]+count×i)]×count/2
  • 查找确定 k k k 属于几位数

  • 通过二分查找确定 k k k 属于 i i i 位数序列的第几个

  • 最后再从这个数所对应的序列里查找 k k k 的位置

  • 这道题目的细节( )有点多,部分细节见代码注释

代码实现

#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <iostream>
using namespace std;
typedef long long ll;
ll q,kk,a[20],b[20];

ll Init(){	
	a[0]=b[0]=0;
	ll i=1;
	ll count=9;
	while(1){
		//i位数的上界 
		a[i]=((b[i-1]+i)+(b[i-1]+count*i))*count/2;
		//最后一个i位数所对应的序列长度 
		b[i]=b[i-1]+count*i;
		count*=10;
		if(a[i]>(ll)pow(10,18))
			break;		
		i++;
	}
	return i;
}

void Solve(ll n,ll k){
	//确定 k 属于几位数 
	ll i;
	for(i=1;i<=n;i++){
		if(k-a[i]<=0) break;
		else k-=a[i];
	}
	//确定 k 属于i位数序列的第几个 
	ll l=1,r=(ll)(9*pow(10,i-1)),mid;
	while(l<=r){
		mid=(l+r)>>1;
		ll sum=((b[i-1]+i)+(b[i-1]+mid*i))*mid/2;
		if(sum>=k)	r=mid-1;
		else	l=mid+1;
	}
	k-=((b[i-1]+i)+(b[i-1]+r*i))*r/2;
	//从这个数所对应的序列中查找 k 的位置  
	ll len=1;
	while(1){
		ll sum=9*pow(10,len-1)*len;
		if(sum>=k) break;
		k-=sum;
		len++;
	}
	//k在第num个len位数中,加上len-1是因为位置可能在一个len位数的中间 
	ll num=(k+len-1)/len;
	//减去前面num-1个len位数,在第num个数中继续搜索位置 
	k-=(num-1)*len;
	//第num个len位数等于几 
	num=pow(10,len-1)+num-1; 
	//位于第几位
	ll cnt=len-k+1;
	for(ll i=1;i<cnt;i++) num/=10;
	printf("%lld\n",num%10);
}

int main()
{
	scanf("%lld",&q);
	ll n=Init();
	while(q--){
		scanf("%lld",&kk);
		Solve(n,kk);
	}
	return 0;
}

暴力打表(60)

#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <map>
#include <algorithm>
#include <string>
#include <cstring>
#include <vector>
#include <cmath>
#include <climits>
using namespace std;
typedef long long ll;
long long a[1000100],kk,t; 

void Init_1(){
	ll sum=0;
	a[0]=0;
	int i=1;
	while(1){
		sum+=log10(i)+1;
		a[i]=a[i-1]+sum;
		if(a[i]>1000000)
			break;
		i++;
	}
}

void Init_2(){
	ll sum = 0;
	a[0]=0;
	int i=1;
	while(1){
		sum+=log10(i)+1;
		a[i]=a[i-1]+sum;
		if(a[i]>(ll)pow(10,18))
			break;
		i++;
	}
}

void Solve(int k){
	ll i=1,j=1,sum=0,res;
	while(a[i]<k) 
		i++;
	res=k-a[i-1];
	sum=0;
	for(j=1;j<=i;j++){
		sum+=log10(j)+1;
		if(sum>=res)
			break;
	}
	if(sum==res) 
		printf("%lld\n",j%10);
	else if(sum>res) 
		printf("%lld\n",(j/(ll)pow(10,sum-res))%10);
}

int main()
{
	scanf("%lld", &t);
	bool flag=false;
	while(t--){
		scanf("%lld", &kk);
		if(!flag){
			if(kk>1000000)
				Init_2();
			else
				Init_1();
			flag=true;
		}	
		Solve(kk);
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值