有趣的数
题目链接:jzoj 3559 / luogu P2022
题目大意
设 Q(N,K) 为将 1~n 的正整数集合的元素按字典序排序,位置是第 K 个的数。
给你 K,M,要你找到最小的 N,使得 Q(N,K)=M。
思路
首先要发现一个性质:
如果位数足够大,排在前面的一定是
0
,
10
,
1000
,
10000
,
.
.
.
0,10,1000,10000,...
0,10,1000,10000,...。
那也就是说,如果出现了一个
n
n
n 位的这样的数,那它的答案一定是 $0
。这个要特判一下,下面在出现这些数会锅。
那接着我们来看,首先不难得到最小也要是原本的数,那原本的数它第几呢?
给一个算的方法:(
234
234
234 为例子)
一位数:
0
∼
2
0\sim2
0∼2(
2
−
0
+
1
=
1
2-0+1=1
2−0+1=1 个)
两位数:
10
∼
23
10\sim23
10∼23(
23
−
10
+
1
=
14
23-10+1=14
23−10+1=14 个)
三位数:
100
∼
234
100\sim234
100∼234(
234
−
100
+
1
=
135
234-100+1=135
234−100+1=135 个)
那不难想到,就是用
1
0
i
−
1
10^{i-1}
10i−1 减从高到低
i
i
i 位加一的结果累加起来。
那如果刚好是
K
K
K,那要的就是
M
M
M,如果小于
K
K
K,那说明无解。
那如果大于,就说明还要往后面放数字,继续加位数。
那我们继续以
234
234
234 为例:
四位数:
1000
∼
2339
(
2340
−
1
)
1000\sim2339(2340-1)
1000∼2339(2340−1)
五位数:
10000
∼
23399
(
23400
−
1
)
10000\sim23399(23400-1)
10000∼23399(23400−1)
那不难看出,就是数字乘
1
0
i
10^{i}
10i(这次
i
i
i 是你多的位数,原本位数是
n
n
n)减去
1
0
i
+
n
10^{i+n}
10i+n 加一。
那如果还是小于
K
K
K,那就还要加,否则答案就在这之间。
你只要
1
0
i
+
n
10^{i+n}
10i+n 加上你剩余还差的部分再减
1
1
1 就可以了。
代码
#include<cstdio>
#define ll long long
using namespace std;
ll k, m, ten[101], tmp, kn;
ll now, addw, newadd;
int main() {
ten[0] = 1;
for (int i = 1; i <= 19; i++)
ten[i] = ten[i - 1] * 10;
scanf("%lld %lld", &k, &m);
for (int i = 0; i <= 9; i++)//特判10...0的情况
if (k == ten[i] && m != i + 1) {
printf("0");
return 0;
}
tmp = k;
while (tmp) {
kn++;
tmp /= 10;
}
for (int i = kn; i >= 1; i--)//计算原本就这个数它前面有多少个(包括他)
now += (k / ten[i - 1]) - ten[kn - i] + 1;
if (now == m) printf("%lld", k);//刚好轮到他
else if (now > m) printf("0");//位置过了
else {
tmp = k;
while (now < m) {
addw++;
tmp *= 10;//不断加 0
newadd = (tmp - 1) - ten[kn + addw - 1] + 1;//判断会新加多少在他前面
if (now + newadd < m) now += newadd;//还可以加
else {//不能加了,按着量计算出到哪刚好是他
printf("%lld", ten[kn + addw - 1] + (m - now) - 1);
return 0;
}
}
}
return 0;
}