洛谷P2022 有趣的数 题解

传送门:洛谷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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值