题目传送门: 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);
}