luogu P2022 有趣的数

题目传送门: https://www.luogu.org/problemnew/show/P2022



题意:

定义一个Q(n,k)=m,表示从1至n的正整数按照字典序排列,第m位为k。
现在给出k,m,求最小的n。 


思路:
对于这道题,我们可以考虑在k的前面放置字典序比它小的数,使放置的数的字典序尽可能少(即最大的数尽可能小),就可以满足题意了。 
可以分解k,预处理出字典序比他小的数的个数p。
1.如果p=m,则结果为k,因为字典序比他小的刚刚好都排在它的前面; 
2.如果p>m,则必定无解(由1可知,p>m时字典序比他小的已经超出了它所容纳的个数);
3.如果p<m,则就要不断地往后拓展,直到找出最小的n。 


解法:

对于分解,很容易,设abc表示一个数,则小于等于它的字典序的个数。 
1~a       -->(a-1)-1+1+1=a-1+1个(设为x个) 
10~ab     -->(ab-1)-10+1+1=ab-10+1个(设为y个) 
100~abc   -->(abc-1-100)+1+1=abc-100+1个(设为z个) 
找出规律了吗?
以上可以很简单地实现。

对于拓展,我们可以想到在上面的基础上,多增加小于等于他的字典序的个数,逐步逼近最优解。
还是以上面的例子为例,我们可以放无数个字典序小于等于它的数,但现在至于要再多放(m-(x+y+z)-1)个即可,为什么要减1呢,因为我们求得是小于等于abc的字典序的个数,也包括它自己,所以要减去。
怎么求扩展个数所对应的n呢。
我们可以看到再往后数下一个最大范围一定为1000~abc(c-1),不能为abcc(字典序比abc大),我们应该减去这一个,又由上面可知,需要减去10^3,才能求出区间个数),后面的以此类推。
就得到了 k*f[i]-f[len+i-1] (k表示数abc,len表示abc的长度3,i就是往后数i个,f[i]为10^i)

最后可能会剩下,就在加上m-1即可。 


代码:

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#define LL long long
	LL m,k,len,ans;
	LL f[30];
void init()
{
	f[0]=1;
	if(k==1&&m==2)
	{
		printf("0");
		exit(0);
	}
	for(int i=1;i<=20;i++)
	{
		f[i]=f[i-1]*10;
		if(k==f[i]&&m!=i+1)
		{
			printf("0");
			exit(0);
		}
	}
}
LL work(LL x)
{
	char s[30];
	LL t=0,sum=0;
	sprintf(s+1,"%lld",x);
	len=strlen(s+1);
	for(int i=1;i<=len;i++)
	{
		t=t*10+(s[i]-48);
		sum+=t-f[i-1]+1;
	}
	return sum;
}
int main()
{
	scanf("%lld %lld",&k,&m);
	init();
	LL p=work(k);
	if(p==m)
	{
		printf("%lld",k);
		return 0;
	}
	if(p>m)
	{
		printf("0");
		return 0;
	}
	ans=f[len];
	m-=p;
	for(int i=1;;i++)
	{
		LL t=k*f[i]-f[len+i-1];
		if(m>t)
		{
			m-=t;
			ans*=10;
		} else break;
	}
	printf("%lld",ans+m-1);
}


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值