【题解】【枚举,全排列枚举】——三连击(升级版)
三连击(升级版)
题目描述
将
1
,
2
,
…
,
9
1, 2,\ldots, 9
1,2,…,9 共
9
9
9 个数分成三组,分别组成三个三位数,且使这三个三位数的比例是
A
:
B
:
C
A:B:C
A:B:C,试求出所有满足条件的三个三位数,若无解,输出 No!!!
。
输入格式
三个数, A , B , C A,B,C A,B,C。
输出格式
若干行,每行 3 3 3 个数字。按照每行第一个数字升序排列。
输入输出样例
输入 #1
1 2 3
输出 #1
192 384 576
219 438 657
273 546 819
327 654 981
提示
保证 A < B < C A<B<C A<B<C。
upd 2022.8.3 \text{upd 2022.8.3} upd 2022.8.3:新增加二组 Hack 数据。
0.前言
我们虽然可以参考[NOIP1998 普及组] 三连击的做法,基本上都没有修改的地方。但是这里还是给大家讲解一下。
解法1.普通枚举
1.1.题意分析
对于这道题,如果我们分别选择枚举a
,b
,c
,那么时间复杂都会达到惊人的
O
(
n
3
)
O(n^3)
O(n3)。
我们可以选择只枚举a
, 通过a
来算出b
和c
。
那么接下来就只需要解决检查是否符合要求的问题了。在这里,我们可以使用类似于计数排序的方法。定义一个pail
数组,pail[i]
表示数字i
出现的次数,我们可以称之为“桶”。如果某个数字没有出现,那么就是不符合要求。
首先定义一个分解数字到“桶”里的go
函数
int pail[10];
void go(int x)//将三位数分解到桶里
{
pail[x%10]++;
pail[x/10%10]++;
pail[x/100]++;
}
然后是检查是否符合要求的check
函数
bool check(int a,int b,int c)
{
memset(pail,0,sizeof(pail));//初始化桶
go(a);go(b);go(c);//分解
for(int i=1;i<=9;i++)//如果有数没有出现就返回假
if(!pail[i])
return 0;
return 1;
}
注意:此题中的测试点中有A=0的情况,因此应该加一个特判。
1.2.AC代码
#include<bits/stdc++.h>
using namespace std;
int pail[10],is_have_ans;
void go(int x)//将一个三位数分解到桶里
{
pail[x%10]++;
pail[x/10%10]++;
pail[x/100]++;
}
bool check(int x,int y,int z)//检查
{
memset(pail,0,sizeof(pail));
go(x);go(y);go(z);
for(int i=1;i<=9;i++)
if(!pail[i])
return 0;
return 1;
}
int main()
{
int a,b,c,A,B,C;
cin>>A>>B>>C;
if(A==0)//如果A为0绝对无解
{
cout<<"No!!!";
return 0;
}
for(a=123;a<=987;a++)
{
if(a*B%A||a*C%A)continue;//如果有余数就不能构成比例
b=a*B/A;c=a*C/A;//算出第二个数和第三个数
if(check(a,b,c))
cout<<a<<' '<<b<<' '<<c<<endl,is_have_ans=1;
}
if(!is_have_ans)//如果无解
cout<<"No!!!";
return 0;
}
解法2.全排列枚举
2.1.题意分析
因为a
,b
,c
总共拥有的数字都只有1~9
,是不变的。所以可以考虑使用STL里的next_permutation
函数。具体语法如下:
next_permutation(数组名+1,数组名+需要排列的长度+1);
它的作用是生成一个排列的下一个字典序稍大的排列。如果这个排列已经是字典序最大的排列,返回0
,否则返回1
。
比如求123
的全排列就可以这样写:
int num[]={0,1,2,3};//这里初始化要多加一个0,用来占位
do
{
cout<<num[1]<<num[2]<<num[3]<<endl;
}while(next_permutation(num+1,num+4));
输出:
123
132
213
231
312
321
如果还不懂可以拿洛谷 P1088 [NOIP2004 普及组] 火星人练练手。
那么我们只需要定义一个数组num
,并不断生成它的下一个排列,再分别计算a
,b
,c
并判断就好了。
2.2.AC代码
#include<bits/stdc++.h>
using namespace std;
int main()
{
int A,B,C,num[15]={0,1,2,3,4,5,6,7,8,9},a,b,c,is_have_ans=0;//数组初始化
cin>>A>>B>>C;
do
{
a=num[1]*100+num[2]*10+num[3];//计算三个数
b=num[4]*100+num[5]*10+num[6];
c=num[7]*100+num[8]*10+num[9];
if(a*B==b*A&&a*C==c*A)//避免除以0的小技巧
cout<<a<<' '<<b<<' '<<c<<endl,is_have_ans=1;
}while(next_permutation(num+1,num+10));//重复生成下一个全排列
if(!is_have_ans)//没有一个解
cout<<"No!!!";
return 0;
}
解法3.深度优先搜索
如果还没学搜索的同学看完上面就已经够用了。如果想要练习深度优先搜索可以看看我下面的解法。
3.1.题意分析
众所周知,搜索是优雅的暴力枚举
这里跟上面的策略一样,使用一个is_have
数组作为“桶”。然后使用一个可变数组vector
来储存a
,b
,c
。
不知道
vector
是什么的同学就把它当普通数组就好了。学有余力的同学也可以把代码改成使用普通数组
然后直接套dfs
模版就行了。
3.2.AC代码
#include<bits/stdc++.h>
using namespace std;
int A,B,C,pail[10],is_have_ans;
vector<int>v;
bool check()//检查是否符合要求
{
memset(pail,0,sizeof(pail));//初始化
for(int i=0;i<9;i++)//将每个出现的数标记下来
pail[v[i]]=1;
for(int i=1;i<=9;i++)//判断是否有数字没有出现
if(!pail[i])
return 0;
int a=v[0]*100+v[1]*10+v[2];
int b=v[3]*100+v[4]*10+v[5];
int c=v[6]*100+v[7]*10+v[8];
if(b*1.0/B*A==a&&c*1.0/C*A==a)//都出现了而且符合A:B:C
return 1;
return 0;
}
void dfs(int step)
{
if(step>9)
if(check())//如果符合要求
{
for(int i=0;i<9;i++)//重复输出
{
cout<<v[i];
if((i+1)%3==0)
cout<<' ';
}
is_have_ans=1;//出现了解,不用输出No!!!
cout<<endl;
return;
}
for(int i=1;i<=9;i++)
if(!pail[i])//如果这个数还没出现
{
v.push_back(i);
pail[i]=1;
dfs(step+1);//递归处理接下来的数
v.pop_back();
pail[i]=0;
}
return;
}
int main()
{
cin>>A>>B>>C;
dfs(1);
if(!is_have_ans)//如果没有出现任何一个解
cout<<"No!!!";
return 0;
}
喜欢就订阅此专辑吧!
【蓝胖子编程教育简介】
蓝胖子编程教育,是一家面向青少年的编程教育平台。平台为全国青少年提供最专业的编程教育服务,包括提供最新最详细的编程相关资讯、最专业的竞赛指导、最合理的课程规划等。本平台利用趣味性和互动性强的教学方式,旨在激发孩子们对编程的兴趣,培养他们的逻辑思维能力和创造力,让孩子们在轻松愉快的氛围中掌握编程知识,为未来科技人才的培养奠定坚实基础。
欢迎扫码关注蓝胖子编程教育