传送门:洛谷P2022 有趣的数
题目描述
有趣的数
题目描述
让我们来考虑1到N的正整数集合。让我们把集合中的元素按照字典序排列,例如当N=11时,其顺序应该为:1,10,11,2,3,4,5,6,7,8,9。
定义K在N个数中的位置为Q(N,K),例如Q(11,2)=4。现在给出整数K和M,要求找到最小的N,使得Q(N,K)=M。
输入格式
输入文件只有一行,是两个整数K和M。
输出格式
输出文件只有一行,是最小的N,如果不存在这样的N就输出0。
样例 #1
样例输入 #1
2 4
样例输出 #1
11
样例 #2
样例输入 #2
100000001 1000000000
样例输出 #2
100000000888888879
提示
【数据约定】
40%的数据,1<=K,M<=10^5;
100%的数据,1<=K,M<=10^9。
思路:
- 数据范围1e9,那么复杂度就在O(n)级别以下。
- 考虑K在N中的位置,不难发现随着N增加,Q(N,K),即K在1~N序列的排名位置单调不减,而且“已知N和K求Q(N,K)” 相对易于计算,于是考虑二分答案。
- 重头戏就是如何快速计算Q(N,K),显然生成所有数再排序O(nlogn)是超时的。
- 我们的整体思路就是数出1~N中字典序小于等于K的数的个数,这就是Q(N,K)的值。
- 我们发现,对于位数相同的两个数,它们的字典序相对大小和两个数本身的相对大小是相同的。例如:
14532 < 20999
- 于是乎我们尝试对于不同位数的数字分析。设序列中需要和K比较大小的数字是X(拿序列中的其中一个数字来比较)。我们发现,如果X位数比K小,那么把X后面全部补上0,就可以使得X和K的字典序大小关系和数字大小关系一样(特殊情况请见下文)。当X的位数比K多的时候,把K补上0,也可以做到这一点。以上内容如果不清楚,建议写几个数字试一试,就明白了。
例如1273 和 12348。1273补上0就变成了12730,12730 > 12348,正好1273的字典序比12348大。
例如45095 和 452。452补上0就变成了45200, 45200 > 45095,正好452的字典序比45095大。
本质上的思想是,数字比大小和字典序是“一致”的,都是从前往后比。只不过数字比大小先比位数,字典序后比位数。
特殊情况:
我们发现我们把X补全成X’的时候,X’字典序比X大,就有可能在“等于”的情况下引发一些小问题。例如:
19 和 1900。前面的19补全变成1900,就和后面的1900一样了。刚才流程判断它们两个字典序相等,但是实际上19的字典序比1900小。
-
而将K补全成K’的时候,由于K’比K大,K’等于X的时候,X大于K,不是我们要统计的东西。
-
于是乎就有这样一种处理策略:对于位数小于等于K的位数的数字,我们统计“小于等于”的数字,而对于位数大于K的数字,我们只统计“小于”K的数字。
-
这里可能需要体会一下。前半部分统计“小于等于”K的数字,其中X’等于K的部分中,有可能原来的X小于K,也有可能等于K,我们一起统计就行(因为X’大于K的时候,X一定大于K,因为X’和K位数相等,严格大于的情况下,一定是X’其中某一位比K的对应位置大,那么原来的X一定大于K)。对于后半部分大于的数字,由于K’大于K,X等于K’的时候,X一定大于K,于是不统计这个X。而X小于K’的时候,由于两者位数相同,一定是X中有位置比K’相应位置小,故X一定比原数K小。(还有一个很重要的原因是,补得数字都是“0”)。
-
如果不明白,请手写几组例子,应该不难理解。
-
代码细节:细节很多。1.判断两种情况:一种是最后K比N还要大,显然是不合法的,输出0;另一种就是算出来Q(N,K)不等于M的,说明找不到答案,输出0。2.r初始值是1e18,原因请看样例2。3.二分答案的相关细节不再赘述,什么l = mid加不加一,while循环条件等等,请读者注意。
附源码
#include <bits/stdc++.h>
using namespace std;
long long UP(long long x)
{
return x < 0? 0:x;
}
long long CUT(long long k,long long len)
{
long long lenth = log10(k) + 1;
return k * pow(10,(len - lenth));
}
long long CHECK(long long n,long long k)
{
//cout << "input:" << k << "\n";
long long lenth = log10(n) + 1;
long long lenk = log10(k) + 1;
long long ans = 0;
long long base = 1;
for(long long i = 1;i <= lenth;i++)
{
if(i <= lenk)
ans += UP(min(n,CUT(k,i)) - base + 1);
else
ans += UP(min(n,CUT(k,i) - 1) - base + 1);
//cout << "CUT:" << k << " " << i << " " << CUT(k,i) << " " << base << " " << ans << "\n";
base *= 10;
}
return ans;
}
int main()
{
/*
cout << CUT(10542,1) << "\n";
cout << CUT(10542,4) << "\n";
cout << CUT(10542,5) << "\n";
cout << CUT(10542,8) << "\n";
*/
//for(int i =1;i <= 205;i++)
//{
// cout << i << " " << CHECK(205,i) << "\n";
//}
//cout << CHECK(26,2);
long long N,K,M;
cin >> K >> M;
//if(K > M)
//{
// cout << 0;
// return 0;
//}
long long l = 1,r = 1e18;
while(l < r)
{
long long mid = (l + r) >> 1;
if(CHECK(mid,K) < M)
l = mid + 1;
else
r = mid;
}
if(CHECK(l,K) != M || l < K)
{
cout << 0;
return 0;
}
cout << l;
return 0;
}