SDU程序设计思维 CSP-M2 C-咕咕东的奇妙序列

SDU程序设计思维 CSP-M2

C-咕咕东的奇妙序列

Description

在这里插入图片描述

Sample

在这里插入图片描述

Idea

题意:对于这个无限序列中,查询第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。

Codes

#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');
	}
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值