codeforces--E1. Numerical Sequence (easy version)--前缀和+双重二分

E1. Numerical Sequence (easy version)
time limit per test2 seconds
memory limit per test256 megabytes
inputstandard input
outputstandard output
The only difference between the easy and the hard versions is the maximum value of k.

You are given an infinite sequence of form “112123123412345…” which consist of blocks of all consecutive positive integers written one after another. The first block consists of all numbers from 1 to 1, the second one — from 1 to 2, the third one — from 1 to 3, …, the i-th block consists of all numbers from 1 to i.

So the first 56 elements of the sequence are “11212312341234512345612345671234567812345678912345678910”. Elements of the sequence are numbered from one. For example, the 1-st element of the sequence is 1, the 3-rd element of the sequence is 2, the 20-th element of the sequence is 5, the 38-th element is 2, the 56-th element of the sequence is 0.

Your task is to answer q independent queries. In the i-th query you are given one integer ki. Calculate the digit at the position ki of the sequence.

Input
The first line of the input contains one integer q (1≤q≤500) — the number of queries.

The i-th of the following q lines contains one integer ki (1≤ki≤109) — the description of the corresponding query.

Output
Print q lines. In the i-th line print one digit xi (0≤xi≤9) — the answer to the query i, i.e. xi should be equal to the element at the position ki of the sequence.

Examples
inputCopy

5
1
3
20
38
56

outputCopy

1
2
5
2
0

inputCopy

4
2132
506
999999999
1000000000

outputCopy

8
2
9
8

Note
Answers on queries from the first example are described in the problem statement.

题意:一个字符串呈现这种规律产生,112123123412345123456
进行q次查询,问第k个字符是什么。

题解:首先观察字符串规律可知道,1,12,123,1234,依次进行拼接,就相当于用i从1开始跑循环,那么就是
i=1:s[1]=1
i=2:s[2]=s[1]+12
i=3:s[3]=s[2]+123
i=4:s[4]=s[4]+1234
我们可以通过这种规律得到前缀和,可以知道,如果我们可以确定第k个字符的区间是在哪个数字结尾的区间里,就可以很快缩小查询范围:
前缀和:
a[1]= 1
a[2]= 3
a[3]= 6
a[4]= 10
a[5]= 15
a[6]= 21
a[7]= 28

分两种情况:
(1)举例如果k=20:
可以知道a[5]=15<k<a[6],那么说明第20个字符在以数字6结尾的这一段子字符串里,也就是123456,此时k-=a[5],得到k=5,那么就是在123456这段中找到第5个字符,也就是5
(2)举例如果k=15:
可以知道a[5]=15=k;
说明第k个字符就是以数字5结尾的子字符串中的最后一个,直接5%10得到5

可以知道前缀和都是增长的,所以这里可以直接二分查找得到对应的区间段,这里是第一次二分。

然后是第二次二分,我们已经知道可以很快得到对应区间段的方法了,现在怎么具体确定是哪一个呢
a[7]= 28
a[8]= 36
a[9]= 45
a[10]= 56
a[11]= 69
a[12]= 84
a[13]= 101
a[14]= 120
a[15]= 141

比如k=115
通过刚才说的可以知道在以14结尾的子字符串内,然后k-=a[13],k=14,相当于现在查询以14结尾形成的字符串内的第14个字符是啥:
14:1234567891011121314
那么是否可以再用一次前缀和,比如
b[1]= 1
b[2]= 2
b[3]= 3
b[4]= 4
b[5]= 5
b[6]= 6
b[7]= 7
b[8]= 8
b[9]= 9
b[10]= 11
b[11]= 13
b[12]= 15
b[13]= 17
b[14]= 19
b[15]= 21

b[x]表示:1+2+3+4+…+x+x+1过程中,到数字x时已经有的数字长度,那么同理的,再利用第一次二分时的思想,再二分直接找到是第几个数字结尾时,那么就针对这个数字求解,比如刚才这个例子,我们刚才经过第一次运算已经知道了k=14,那么在b中,b[11]<k<b[12],说明第14个字符一定12里面,然后k-=b[11],k=1,就说明是12里面的第一个数字,也就是1,思路就是这样。

AC代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll a[1000000],b[1000000];
void init()
{
	ll ans=0;
	ll sum=0;
	memset(a,0,sizeof(a));
	memset(b,0,sizeof(b));
	for(int i=1;i<=1000000;i++)
	{
		if(i>=1&&i<=9)
		ans+=1;
		else if(i>=10&&i<=99)
		ans+=2;
		else if(i>=100&&i<=999)
		ans+=3;
		else if(i>=1000&&i<=9999)
		ans+=4;
		else if(i>=10000&&i<=99999)
		ans+=5;
		
		sum+=ans;
		a[i]=sum;
	//	cout<<i<<" "<<sum<<endl;
		if(a[i]>1e9)
		{
	//		cout<<i<<endl;
			break;
		}	
	}
	ans=0;
	for(int i=1;i<=51836;i++)
	{
		if(i>=1&&i<=9)
		ans+=1;
		else if(i>=10&&i<=99)
		ans+=2;
		else if(i>=100&&i<=999)
		ans+=3;
		else if(i>=1000&&i<=9999)
		ans+=4;
		else if(i>=10000&&i<=99999)
		ans+=5;
		b[i]=ans;
	//	cout<<i<<" "<<ans<<endl;
	}
}
int main()
{
	init(); 
	int q;
	cin>>q;
	while(q--)
	{
		int k;
		scanf("%d",&k);
		int l=1,r=21836,fin;
		while(l<=r)
		{
			int mid=(l+r)/2;
			if(a[mid]<=k)
			{
				l=mid+1;
				fin=mid;
			}
			else r=mid-1;
		}
		k-=a[fin];
		if(k==0)
		{
			printf("%d\n",fin%10);
		}
		else
		{
			int l=1,r=21836,fin;
			while(l<=r)
			{
				int mid=(l+r)/2;
				if(b[mid]<=k)
				{
					l=mid+1;
					fin=mid;
				}
				else r=mid-1;
			}
			k-=b[fin];
			if(k==0)
				printf("%d\n",fin%10);
			else
			{
				fin+=1;
				vector<int>p;
				p.clear();
				while(fin>0)
				{
					p.push_back(fin%10);
					fin/=10;	
				}	
				printf("%d\n",p[p.size()-k]);
			}
		}
	} 
	return 0; 
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值