Codeforces Round #587 (Div. 3) E.Numerical Sequence(easy:前缀和+二分 hard:等差数列+分段+二分)

题目

给定一个数列,1 12 123 1234,无限往后延伸,到10的时候10算两个字符,以此类推

q(q<=500)个询问,第i次询问第ki个字符是什么

①easy version: ki<=1e9

②hard version: ki<=1e18

题解

相同的思想是,先二分去掉二重前缀,确定k落在一个1到x的段里,把1到x-1前面所有段都减掉

再二分一次去掉单重前缀,确定k落在[1,x]段的y值上,把1到y-1前面所有值都减掉

最后用sprintf把y值搞进字符串,输出第k位即可

Easy:

天下好题一大抄,UVA10706原题警告

考虑到N大概到3W多,就会出现1e9字符,所以可以按值做

统计a[i]统计1到x需要多少字符,sum[i]对a[i]求前缀和,先二分sum再二分a最后输出第k个值

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=35000;
int q,k,pos;
ll a[N],sum[N];
char s[11];
void init()
{
	for(int i=1;i<N;++i)
	{
		a[i]=a[i-1]+log10(i)+1;
		sum[i]=sum[i-1]+a[i]; 
	}	
}
int main()
{
	init();
	scanf("%d",&q);
	while(q--)
	{
		scanf("%d",&k);
		pos=lower_bound(sum,sum+N,k)-sum;
		k-=sum[pos-1];
		pos=lower_bound(a,a+N,k)-a;
		k-=a[pos-1];
		sprintf(s+1,"%d",pos);
		printf("%c\n",s[k]);
	}
	return 0;
}

Hard:

由于k到1e18,所以大概值大概到1e9,上述方法不可取,考虑按位做

L[i],R[i]分别确定i位的数的左右界,左闭右开,如L[2]代表10,R[2]代表100

l[i],r[i]用于确定i位的数最小和最多需要多少位,如l[2]代表1到10的位数和,r[i]代表1到99的位数和

sum[i]代表小于等于i位的所有的字符的个数和,sum[2]代表1 12 ...到以99结尾的字符和

 

多了一次对位数的分类讨论,先枚举出所在段[1,left]的位,减去前面比left小的位的和

再用等差数列搞掉和left相等的位的和,然后说明k落在[1,left]里的值上,

此时令right=left,left=1,再开始二分y值,把[1,y-1]的值都减掉

减掉的方法,可以纵向统计每个位出现的个数,在cal函数里

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int q;
ll sum[10],l[10],r[10],L[10],R[10],k,ans,lef,rig;
char s[11];
void init()
{
	ll now=1;
	for(int i=1;i<=9;++i,now*=10)
	{
		L[i]=now;R[i]=10*L[i];//[L[i],R[i])
		l[i]=r[i-1]+i;r[i]=r[i-1]+(R[i]-L[i])*i;
		sum[i]=sum[i-1]+(l[i]+r[i])*(R[i]-L[i])/2;
	}
}
ll cal(ll mid)
{
	ll v=0;
	for(int j=1;j<=9;++j)
	{
		if(mid>=L[j])v+=mid-L[j]+1;
		else break;
	}
	return v;
}
int main()
{
	init();
	scanf("%d",&q);
	while(q--)
	{
		scanf("%lld",&k);
		int i;
		for(i=1;i<=9;++i)
			if(sum[i]>=k)break;
		k-=sum[i-1];
		lef=L[i],rig=R[i];
		while(lef<=rig)
		{
			ll mid=(lef+rig)/2,v=l[i]+(mid-L[i])*i;
			if((l[i]+v)*(mid-L[i]+1)/2>=k)rig=mid-1;
			else lef=mid+1;
		}
		k-=(l[i]+l[i]+(lef-1-L[i])*i)*(lef-L[i])/2;
		rig=lef,lef=1;
		while(lef<=rig)
		{
			ll mid=(lef+rig)/2;
			if(cal(mid)>=k)rig=mid-1;
			else lef=mid+1;
		}
		k-=cal(lef-1);
		sprintf(s+1,"%lld",lef);
		printf("%c\n",s[k]);
	}
	return 0;
} 

心得

hard是自己莽过去的,大概学了一下easy的便捷做法,

log(10)+1统计位数,sprintf的用法等等

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小衣同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值