2019年9月 PAT甲级 7-1 Forever 题解

该博客介绍了2019年9月PAT甲级考试中的一道难题,要求找到所有K位数,使得数字和为m,加1后的数字和为n,并且m和n的最大公约数是大于2的质数。博主通过分析得出结论,只有存在进位的情况才可能满足条件,进而缩小枚举范围,通过枚举尾数为9的数并结合数学计算,最终实现问题的解决。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

2019年9月 PAT甲级 7-1 Forever 题解

题意

输入N对K和m,要求找到所有K位数满足:
1.该数的和为m;
2.该数+1的和为n;
3.m和n的最大公约数是一个大于2的质数;

思路

K<10,最大为9,暴力枚举是10的9次,肯定超时,需要缩小枚举范围。

看到这题,我觉得一种数学直觉是大家都会有的,那就是:任意一个数p和p+1一般都是互质的吧。事实也是如此(我一时没想到反例)。

这就意味着,要是一个数简单+1,若没有进位,那么肯定无法符合要求,因为这样n和m也只相差一,它们是互质的(最大公约数为1,不是大于2的质数)。于是,我们就知道了,我们要枚举的是有进位的那些数。也就是尾数为9、99、999、9999……的那些数。而且,更有趣的是,我们可以根据尾数情况直接得到n和m的差值!举个例子9+1=10,位数和从9到1,m-n=8;99+1=100,位数和从18到100,m-n=17。(代码里的yes函数就在判断这个)

考虑到位数和的范围是其实很小,是1-90,也就是说m和n都是1-90;那我们就可以直接去算1-90这些数,哪些组合是符合条件3的?可以直接打表看。这就是我下面init函数里做的事儿。处理结果mn[i][j]为true表明(i,j)是符合要求的(m,n)对。事实上,根据m、n的差值和mn数组,可以进一步缩小可能的数位和,m和n的差因为是进位造成的,所以是有限制的,只能是17、26、35、44这些数(可以通过我注释掉的代码验证)。也就是说,我们只需要枚举以99、999、9999、99999为位数的数即可。

那剩下来的任务就是枚举了。为了控制好不同位数9尾数的进位,我们需要对前面剩余x个数位从10的x-1次方(形式为10…0)枚举到10的x次方减2(形式为99…98)。枚举代码在main函数里,草稿纸上写写对应一下下标就出来了。枚举并根据mn数组判断,最后把答案加到set里并输出。

至此,本场考试最难的一道题成功AC!(我没看AC率,做到后面才知道这道题过的人最少)代码如下:

#include <iostream>
#include <string>
#include <cstdio>
#include <memory.h>
#include <algorithm>
#include <cstring>
#include <vector>
#include <map>
#include <queue>
#include <set>
using namespace std;

bool mn[100][100];//[i][j]表示(i,j)是符合要求的数对。 

int gcd(int a,int b){//辗转相除法求最大公约数 
	if(b==0)return 2;
	return a%b==0?b:gcd(b,a%b);
}

bool isPrime(int n){//判断n是否为质数 
	for(int i=2;i<n;i++){
		if(gcd(n,i)!=1)return false;
	}	
	return true;
} 

void init(){//预先达标符合要求的m、n对 
	for(int i=2;i<=90;i++){
		for(int j=2;j<=90;j++){
			int GCD=gcd(i,j);
			if(GCD>2&&isPrime(GCD)){
//				cout<<"("<<i<<","<<j<<")"<<endl;
				mn[i][j]=true;
			}
		}
	}
}

int sum(int n){//求数位和 
	int ret=0;
	do{
		ret+=n%10;
		n/=10;
	}while(n);
	return ret;
}

int POW(int n){//返回10的n次方 
	int ret=1;
	while(n--)ret*=10;
	return ret;
}

//bool yes(int n){
//	return n==8||n==17||n==26||n==35||n==44||n==53||n==62||n==71||n==80;
//}

int main(){
	int N,K,m;
	cin>>N;
	init();
	
//	for(int i=1;i<=90;i++){//好奇的人可以看一下 
//		for(int j=1;j<=90;j++){
//			if(yes(i-j)&&mn[i][j])cout<<"("<<i<<","<<j<<")"<<i-j<<endl;
//		}
//	}

	for(int Case=1;Case<=N;Case++){
		cin>>K>>m;
		set<pair<int,int> >ans;
		cout<<"Case "<<Case<<endl;
		for(int i=2;i<=min(5,K-1);i++){
			for(int j=POW(K-i-1);j<=POW(K-i)-2;j++){
				int num=j*POW(i)+POW(i)-1;
				if(num>=POW(K-1)&&num<POW(K)-1&&sum(num)==m){
					int sumi=sum(num),sumip1=sum(num+1);
					if(mn[sumi][sumip1]){
						ans.insert(make_pair(sumi,num));
					}
				}
			}
		}
		if(ans.size()==0){
			cout<<"No Solution"<<endl;
			continue;
		}
		for(set<pair<int,int> >::iterator it=ans.begin();it!=ans.end();it++){
			cout<<it->first<<" "<<it->second<<endl;
		}
	}
	return 0;
} 

/*
输入N对K和m,要求找到所有K位数满足:
1.该数的和为m;
2.该数+1的和为n;
3.m和n的最大公约数是一个大于2的质数; 
*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Steven 周

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值