Week8 CSP模拟 T4咕咕东的奇妙序列

题目描述

咕咕东 正在上可怕的复变函数,但对于稳拿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),接下来第i+1行表示第i个输入ki​,表示询问第ki​项数字。(1<=ki<=10^18)

输出格式

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

样例输入

5
1
3
20
38
56

样例输出

1
2
5
2
0

思路

这道题首先一看是没有什么思路,就尝试打表处理,两层for循环,

    for(int i=1;i<900;i++)
    for(int j=1;j<=i;j++)
    {
        t1=j;
        if(t1>=10&&t1<=99)
        {
           t=t1/10;
           t1%=10;
           openfile<<t<<","<<t1<<",";
           cnt+=2;
        }
	  else if(t1>=100)
	  {
	     t=t1/100;
	     t1=t1-100*t;
	     t2=t1/10;
	     t1%=10;
	     openfile<<t<<","<<t2<<","<<t1<<",";
	     cnt+=3;
	  }
	  else
	  {
	     openfile<<t1<<",";
	     cnt+=1;
	  }
}

在外层for循环为900时,会得到1e6个数,可以骗过前六个点,但是对于后面的1e6~1e18就不能再采用打表了,通过请教其他同学,得知一种二分查找的思路
对于这个无限序列中,查询第k项数字,1<=k<=1e18,考虑到k庞大的数量级,打表时我们采用10的幂次作为每部分,每次查询单独打表。
首先确定用a[x]代表1-X的长度,X∈[1ed,1e(d+1)),a的每次变化遵循等差数列,公差为d+1,用sum[x]代表最开始到第一次出现X的总长度,sum的每次变化遵循等差数列a的求和,ans代表当前的数值X。将X分成个位数,十位数,百位数,…,n位数来计算,即1-9,10-99,100-999,…为每部分。对于给定的X,找到它所在的数量级,根据等差数列公式求出X对应的a,根据等差数列求和公式求出X对应的sum。
有了以上的基础,对于每次查询第k位,先二分查找第一个sum[ans]>=k的ans,那么答案在无限序列中位于从1到ans的的那一段,则k=k-sum[ans-1]定位1-ans这一段,再二分查找第一个a[ans]>=k的ans,则答案就在数值等于ans的那个数中,则k=k-a[ans-1]定位ans这个数,两次查找并更新k后可以确定ans的第k位就是要求的答案。

Summary
这道题比较难,基本思想是二分查找+打表。一开始是考虑先打表后查询,但一个数组不能开到包含1e18个位的数对应的个数,容易造成数组越界,即无法在预处理时存储整个表。因此把打表放进每次查询中避免RE,每次打表至与给定的X同一数量级即可确定X对应的a和sum,二分查找将时间复杂度降至O(logn)避免超时,先定位1-ans这一段,然后定位ans这个数,每次定位更新k。

代码

#include<iostream>
#include<iostream>
#include<cstdio>
#include<cmath>
#include<string>
using namespace std;
long long q, k, l, r, mid, ans;
long long solve(long long x, int cho) {
	long long pow = 1, sum = 0, a = 0, n = 0, d = 0;
	while (true) {
		pow *= 10; d++;
		if (x > pow - 1) {
			//x的数量级大于pow
			n = pow - pow / 10;
			sum += (a + d)*n + n * (n - 1) / 2 * d;
			a += n * d;
		}
		else {
			//x的数量级等于pow
			n = x - pow / 10 + 1;
			sum += (a + d)*n + n * (n - 1) / 2 * d;
			a += n * d;
			break;
		}
	}
	return cho == 1 ? sum : a;
}
int main() {
	cin >> q;
	while (q--) {
		cin >> k;
		l = 0; r = 1e9;
		while (l <= r) {
		//寻找第一个>=k的
			mid = (l + r) >> 1;
			if (solve(mid, 1) >= k) ans = mid, r = mid - 1;
			else l = mid + 1;
		}
		k -= solve(ans-1, 1);
		l = 0; r = ans + 1;
		while (l <= r) {
		//寻找第一个>=k的 
			mid = (l + r) >> 1;
			if (solve(mid, 2) >= k) ans = mid, r = mid - 1;
			else l = mid + 1;
		}
		k -= solve(ans-1, 2);


		//第ans个数的第k位
		string s = to_string(ans);
		printf("%d\n", s[k - 1] - '0');
	}
	return 0;
} 
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值