PTAL1-033 出生年(15 分)超级详解与双算法

“我出生于1988年,直到25岁才遇到4个数字都不相同的年份。”也就是说,直到2013年才达到“4个数字都不相同”的要求。本题请你根据要求,自动填充“我出生于y年,直到x岁才遇到n个数字都不相同的年份”这句话。
输入格式:
输入在一行中给出出生年份y和目标年份中不同数字的个数n,其中y在[1, 3000]之间,n可以是2、或3、或4。注意不足4位的年份要在前面补零,例如公元1年被认为是0001年,有2个不同的数字0和1。
输出格式:
根据输入,输出x和能达到要求的年份。数字间以1个空格分隔,行首尾不得有多余空格。年份要按4位输出。注意:所谓“n个数字都不相同”是指不同的数字正好是n个。如“2013”被视为满足“4位数字都不同”的条件,但不被视为满足2位或3位数字不同的条件。
输入样例1:
1988 4
输出样例1:
25 2013
输入样例2:
1 2
输出样例2:

0 0001

本题的难点在于理解“数字不同”这句话,如果理解存在偏差,则分数必不会高,因为一开始的算法策略就错了。本人第一次做此题时,将“数字不同”理解为“当前位上的数字与任意剩余位上的数字都不同”,其实题目中的一个细节已经可以推翻这个结论——"公元1年被认为是0001年,有2个不同的数字0和1",也就是说,在四个数字0001中,0和1是不同的,故不同的数字数量为2,而不去管某一位上的0与其他位上的0重复而不将0视为"数字不同",即,我们从0001中剔除了重复的0,得到0和1,这样的过程,如果敏锐一点,会发现就是将数字加入集合的过程。

我们知道集合中的元素是不会重复的,故结合本题,可设一数组,下标为各个位上的数字,每一元素为0或1,0表示当前位的数字还没有加入集合,1表示当前位上的数字已经加入集合,故可通过判定数组元素是否为0来决定将数字加入集合的操作,如果元素不为0,说明已经当前数字已经加入集合,不执行,如果元素为0,则将数字加入集合即将元素置1。当然,我们还需要一个计数器来统计有多少数字加入集合,即集合的大小是多少,若集合的大小等于要求的n,则此数字满足条件。其核心算法为:

while(num)
{
int t=num%10;
if(!Set[t])//若当前位上的数字t未加入集合 
{
Set[t]=1;//将t加入集合
size++;//集合大小+1 
}
num/=10;

}

有了核心算法还不够,我们还需要重新审阅一下题目以查看是否有遗漏的细节。我们注意到:“不足4位的年份要在前面补零”,于是我们需要在输出的时候以4位数字的格式输出,不足补0,右对齐。

分析完核心算法和题目给出的细节,可能还不够,我们需要更为细致,敏锐,分析更加透彻才能把握完整正确的解题方向。于是我们还注意到一个隐藏细节:若数字小于1000,因为题目始终要求的是对4位数字进行判定,所以数字会有前导0,故0必然需要加入集合,其处理算法为:

if(num<1000)//若数字小于1000,则数字中必然存在前导0
{
Set[0]=1;//将0加入集合 
size++;//集合大小+1 

}

至此,我们已经把握了完整正确的解题方向与核心算法,如果态度仔细,必定能一次性AC,以下是具体代码:

#include<iostream>
#include<iomanip>
using namespace std;
bool CHECK(int num,int n)
{/*定义集合数组,下标为num每一位上的数字
	元素为0或1,0代表未加入集合,1代表已加入集合 */
	int Set[10]={0},size=0;//定义集合大小size 
	if(num<1000)//若数字小于1000,则数字中必然存在前导0
	{
		Set[0]=1;//将0加入集合 
		size++;//集合大小+1 
	}
	while(num)
	{
		int t=num%10;
		if(!Set[t])//若当前位上的数字t未加入集合 
		{
			Set[t]=1;//将t加入集合
			size++;//集合大小+1 
		}
		num/=10;
	}
	if(size==n)//若集合大小等于n,则num满足条件 
	return true;
	return false;
}
int main()
{
	int num;
	int n;
	cin>>num>>n;
	int i=num;
	while(!CHECK(i,n))//如果不满足条件,则增加年份,继续循环 
	i++;
	cout<<i-num<<' '<<setw(4)<<setfill('0')<<right<<i;
	return 0;
}

如果熟悉C++STL的同学,可以使用set来进行更为快速地解题,以下是使用set的具体代码:

#include<iostream>
#include<iomanip>
#include<set>
using namespace std;
int main()
{
	int n,num;
	set<int>s;
	cin>>num>>n;
	int i=num-1;
	while(s.size()!=n)//若集合大小不为n
	{
		s.clear();//清空集合,以待填充 
		int t=++i;//先增i,使i与循环判定状态一致 
		if(t<1000)
		s.insert(0);//若数字小于1000,则数字中必然存在前导0,将0加入集合 
		while(t)
		{
			s.insert(t%10);//将每一位上的数字加入集合 
			t/=10;
		}
	}
	cout<<i-num<<' '<<setw(4)<<setfill('0')<<right<<i;
	return 0;
}

解题,首先抓住并处理好题目的每一个细节,如果题目的细节理解错了,那么一开始的算法策略是错的,你将无法AC。处理好题目的细节之后,再思考还有哪些隐藏的细节需要你挖掘,比如此题的“前导0”,是对你程序输入输出特性和态度的一种考察,如果你足够细致,你会注意到这点并处理。当你处理完所有细节之后,你还可能需要考虑特殊情况,即输入数据较特殊的时候,你也需要将其列入考虑范围,只有这样,你才能一次性AC。

综上,AC=处理好所有细节+处理好所有可能的特殊情况。

猜你喜欢

转载自blog.csdn.net/qq_37729102/article/details/80825421