7-1 Forever (20分)
"Forever number" is a positive integer A with K digits, satisfying the following constrains:
- the sum of all the digits of A is m;
- the sum of all the digits of A+1 is n; and
- the greatest common divisor of m and n is a prime number which is greater than 2.
Now you are supposed to find these forever numbers.
Input Specification:
Each input file contains one test case. For each test case, the first line contains a positive integer N (≤5). Then N lines follow, each gives a pair of K (3<K<10) and m (1<m<90), of which the meanings are given in the problem description.
Output Specification:
For each pair of K and m, first print in a line Case X
, where X
is the case index (starts from 1). Then print n and A in the following line. The numbers must be separated by a space. If the solution is not unique, output in the ascending order of n. If still not unique, output in the ascending order of A. If there is no solution, output No Solution
.
Sample Input:
2
6 45
7 80
Sample Output:
Case 1
10 189999
10 279999
10 369999
10 459999
10 549999
10 639999
10 729999
10 819999
10 909999
Case 2
No Solution
题目限制:
题目大意:
现给定一个数字A的总位数K和所有位数之和m,要求你寻找这样的一个数字n,n为A+1的所有数位之和,m和n的最大公约数G,并G大于2且为素数,输出所有的n和A,否则输出No Solution。
算法思路:
第一反应此题和全排列的问题即为相似,就是添加了约束条件。那么其本质就是使用暴力搜索,很容易就想到使用DFS进行求解。但是直接进行DFS最后一个点会超时,所以得弄清楚剪枝条件,我们假设现在搜索到第depth
位,当前的总数位之和为m
,数字为num
,那么后面还没有搜索的数位有K-depth
位。那么剪枝的条件就是,如果当前的总数位之和m+后面未搜索的最大总数位之和9*(K-depth)
小于K
,那么就说明无论怎样取值,都不可能存在一个数的总数位m符合题目要求。这样DFS的超时问题就解决了,现在只需要求解任意2数的最大公约数和任意一个数字的位数之和,就可以编写DFS函数了。
求解最大公约数的方法,这里给出2种:
// 适用于a,b较小的情况
int gcd(int a,int b){
if(b==0) return a;
return gcd(b,a%b);
}
// 适用于a,b较大的情况(编程之美2.7章节)
int gcd(int a,int b){
if(a<b) return gcd(b,a);
if(b==0){
return a;
} else {
if(a%2==0){
if(b%2==0){
return 2*gcd(a/2,b/2);
} else {
return gcd(a/2,b);
}
} else{
if(b%2==0){
return gcd(a,b/2);
} else {
return gcd(b,a-b);
}
}
}
}
对于任意一个数字num,其总位数之和:
int getDigitSum(int num){
int m = 0;
while (num!=0){
m += num%10;
num /= 10;
}
return m;
}
DFS函数:
/*
* depth:当前深度,代表选择第几位,初始为0
* m:当前已经选择的位数之和,比如选择了3次,分别是1,4,5,那么m=1+4+5=10
* num:当前已经选择的位数所代表的数字,比如选择了3次,分别是1,4,5,那么num=145
*
*/
void DFS(int depth,int m,int num){
if(depth==K&&m==M){
int n = getDigitSum(num+1);// 得到n
int G = gcd(m,n);
if(G>2&&isPrime(G)){
// 保存结果集
result.emplace_back(num,n);
}
return;
}
// 剩余最大值之和都小于所需要的,不可能有解
if(m+9*(K-depth)<M||depth>K) return;
for(int i=0;i<=9;++i){
DFS(depth+1,m+i,num*10+i);
}
}
到这里DFS并没有结束,因为还有可以优化的点(上面的代码已经可以通过测试了),因为第一位一定是1,不可能是0,最后两位一定是99。第一位是1比较好想的到,最后两位为什么是99呢?
首先对于任意2个相邻数字的最大公约数为1,比如6和7,要想m和n的最大公约数大于2,首先就是要求m和n不能是相邻的两个数字,那么要求m和n不相邻,就得保证A的最后一个数字得是9,不然A+1没有进位,n=m+1。但是如果只有最后一位为9依然还是不行,因为如果只有一个9,那么A+1的所有数位之和n=m-9+1(最后的9进位了),那么n与m的最大公约数等价于8和m的最大公约数(辗转相除法,不懂得看编程之美2.7章节),但是8的约数只有1,2,4,8,均不是大于2的素数,所以最后一位还得是9,因为不是9的化,n依然等于m-8。最后2位为9以后,n=m-9-9+1(2个9都进位了),这样n和m的公约数就转化为m和17的公约数了,如果m恰好整除17,就得到了一个大于2的素数,所以最后2位一定是9。
根据以上分析,优化后的DFS如下:
/*
* depth:当前深度,代表选择第几位,初始为0
* m:当前已经选择的位数之和,比如选择了3次,分别是1,4,5,那么m=1+4+5=10
* num:当前已经选择的位数所代表的数字,比如选择了3次,分别是1,4,5,那么num=145
*
*/
void DFS(int depth,int m,int num){
if(depth==K&&m==M){
int n = getDigitSum(num+1);// 得到n
int G = gcd(m,n);
if(G>2&&isPrime(G)){
result.emplace_back(num,n);
}
return;
}
// 剩余最大值之和都小于所需要的,不可能有解
if(m+9*(K-depth)<M||depth>K) return;
// 第一位一定为1,最后2位一定为9
int i = depth==0 ? 1: depth==K-1||depth==K-2? 9 : 0;
for(;i<=9;++i){
DFS(depth+1,m+i,num*10+i);
}
}
最后使用result保存所有的结果集,然后排序输出即可。
注意点:
- 1、排序是必须的,不然测试点2无法通过(卡了我1小时,原本以为已经有序)
提交结果:
AC代码:
#include<cstdio>
#include<vector>
#include<cmath>
#include <algorithm>
using namespace std;
struct Node{
int A;
int n;
Node(int _A,int _n){
A = _A;
n = _n;
}
};
int K,M;// 总位数,总位数之和
vector<Node > result;
bool cmp(const Node &a,const Node &b){
return a.n!=b.n?a.n<b.n:a.A<b.A;
}
int gcd(int a,int b){
if(b==0) return a;
return gcd(b,a%b);
}
bool isPrime(int n){
if(n<=1) return false;
int sqrtn = (int)sqrt(n*1.0);
for(int i=2;i<=sqrtn;++i){
if(n%i==0) return false;
}
return true;
}
int getDigitSum(int num){
int m = 0;
while (num!=0){
m += num%10;
num /= 10;
}
return m;
}
/*
* depth:当前深度,代表选择第几位,初始为0
* m:当前已经选择的位数之和,比如选择了3次,分别是1,4,5,那么m=1+4+5=10
* num:当前已经选择的位数所代表的数字,比如选择了3次,分别是1,4,5,那么num=145
*
*/
void DFS(int depth,int m,int num){
if(depth==K&&m==M){
int n = getDigitSum(num+1);// 得到n
int G = gcd(m,n);
if(G>2&&isPrime(G)){
result.emplace_back(num,n);
}
return;
}
// 剩余最大值之和都小于所需要的,不可能有解
if(m+9*(K-depth)<M||depth>K) return;
// 第一位一定为1,最后2位一定为9
int i = depth==0 ? 1: depth==K-1||depth==K-2? 9 : 0;
for(;i<=9;++i){
DFS(depth+1,m+i,num*10+i);
}
}
int main(){
int N;
scanf("%d",&N);
for(int i=0;i<N;++i){
scanf("%d %d",&K,&M);
printf("Case %d\n",i+1);
result.clear();
DFS(0,0,0);
sort(result.begin(), result.end(),cmp);
for (auto &item : result) {
printf("%d %d\n",item.n,item.A);
}
if (result.empty()) {
printf("No Solution\n");
}
}
return 0;
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。