穷举(二):直接确定区间穷举
在采用穷举法解决问题时,大多数时候可以确定穷举的范围,即待解决问题有明确的区间限制,可以采用循环在这个指定的范围内搜索满足约束条件的解。
【例2】数字方格
有3个方格,每个方格里面都有一个整数a1,a2,a3。已知0<=a1,a2,a3<=n,而且a1+a2是2的倍数,a2+a3是3的倍数,a1+a2+a3是5的倍数。编写一个程序,输入一个n(0 <= n<=100),找到一组a1、a2、a3,使得a1+a2+a3最大。
(1)编程思路。
用3重循环直接对0~n范围内的a1、a2、a3进行穷举。在内循环中检测约束条件((a1+a2)%2==0) && ((a2+a3)%3==0) && ((a1+a2+a3)%5==0)是否满足,若满足,再看是否需要更新max的值(max初值可设置为0)。
(2)源程序及运行结果。
#include <iostream>
using namespace std;
int main()
{
int a1,a2,a3,n,max,x,y,z;
max=0;
cin>>n;
for(a1=0;a1<=n;a1++)
for(a2=0;a2<=n;a2++)
for(a3=0;a3<=n;a3++)
{
if (((a1+a2)%2==0) && ((a2+a3)%3==0) && ((a1+a2+a3)%5==0))
{
if((a1+a2+a3)>max)
{
max=a1+a2+a3;
x=a1; y=a2; z=a3;
}
}
}
cout<<"max="<<max<<endl;
cout<<"a1="<<x<<",a2="<<y<<",a3="<<z<<endl;
return 0;
}
(3)问题扩展。
上面的程序可以求出a1+a2+a3的最大值,以及达到最大值时a1、a2、a3的取值情况。但实际上,对于给定的n,当a1+a2+a3达到最大值时,a1、a2、a3的取值组合可能有多种。例如,n=5时,a1+a2+a3的最大值为10,达到最大值时,满足要求的a1、a2、a3的取值组合有(1,5,4)、(4,2,4)和(4,4,2)三种。能否对程序进行扩展连营,输出所有的组合情况呢?
为保存所有的组合情况,程序中可以定义一个二维数组。在max更新时,对数组进行更新。
(4)扩展问题的源程序及运行结果。
#include <iostream>
using namespace std;
int main()
{
int a1,a2,a3,n,i,max,cnt;
int a[100][3];
max=0; cnt=0;
cin>>n;
for(a1=0;a1<=n;a1++)
for(a2=0;a2<=n;a2++)
for(a3=0;a3<=n;a3++)
{
if (((a1+a2)%2==0) && ((a2+a3)%3==0) && ((a1+a2+a3)%5==0))
{
if((a1+a2+a3)>max)
{
cnt=0; max=a1+a2+a3;
a[cnt][0]=a1; a[cnt][1]=a2; a[cnt][2]=a3;
}
else if ((a1+a2+a3)==max)
{
cnt++;
a[cnt][0]=a1; a[cnt][1]=a2; a[cnt][2]=a3;
}
}
}
cout<<"max="<<max<<endl;
for (i=0;i<=cnt;i++)
cout<<"("<<a[i][0]<<","<<a[i][1]<<","<<a[i][2]<<")"<<endl;
return 0;
}
【例3】猜年龄
美国数学家维纳(n.wiener)智力早熟,11岁就上了大学。一次,他参加某个重要会议,年轻的脸孔引人注目。于是有人询问他的年龄,他回答说:“我年龄的立方是个4位数。我年龄的4次方是个6位数。这10个数字正好包含了从0到9这10个数字,每个都恰好出现1次。”
请你推算一下,他当时的年龄是多少?
(1)编程思路。
因为10的立方为最小的4位数,而23的立方为12167是个5位数,所以可以在10~22之间进行年龄的穷举。
定义一个数组int a[10]用于保存数字0~9的出现情况,每次穷举时均赋初值0。对每个年龄i,计算i的立方num1和4次方num2,然后将num1和num2的各位数字分解出来,相应置数组元素a[k]=1,若某个a[k]被两次或多次置1,则不符合要求。
(2)源程序及运行结果。
#include <iostream>
using namespace std;
int main()
{
int a[10],i,k,num1,num2;
for(i=10; i<22;i++)
{
num1=i*i*i;
num2=i*i*i*i;
for (k=0;k<=9;k++) a[k]=0;
while(num1!=0)
{
if(a[num1%10]==0)
{
a[num1%10]=1;
num1=num1/10;
}
else
break;
}
while(num2!=0){
if(a[num2%10]==0){
a[num2%10]=1;
num2=num2/10;
}
else
break;
}
if(num1==0 && num2==0)
cout<<i<<endl;
}
return 0;
}
【例4】珠心算测验
珠心算是一种通过在脑中模拟算盘变化来完成快速运算的一种计算技术。珠心算训练,既能够开发智力,又能够为日常生活带来很多便利,因而在很多学校得到普及。
某学校的珠心算老师采用一种快速考察珠心算加法能力的测验方法。他随机生成一个元素个数不超过100个的正整数集合,集合中的数的大小不超过1000,且各不相同,然后要求学生回答:其中有多少个数,恰好等于集合中另外两个(不同的)数之和?
例如,对于集合{1,2,3,4,5},输出应为3。因为3=1+2、4=2+2=1+3、5=1+4=2+3,共3个数。
(1)编程思路。
定义一个数组int flag[2001]用于记录和的出现情况。初始时元素值全为0。
将集合中每两个不同的数穷举相加求和,再将这个和打上标记。例如,若数a与数b相加等于数c,则置flag[c]=1。
之后,扫描所有集合中的数x,统计所有被打上标记的数(即flag[x]==1)的数量。
(2)源程序及运行结果。
#include <iostream>
using namespace std;
int main()
{
int n,i,j,x,a[101],flag[2001]={0},cnt;
cout<<"请输入拟产生集合中数据元素的个数:";
cin>>n;
i=0;
while (i<n)
{
x=rand()%1000+1;
if (flag[x]==0)
{
a[i++]=x;
flag[x]=1;
}
}
cout<<"随机产生的"<<n<<"个整数如下:"<<endl;
for (i=0;i<n;i++)
cout<<a[i]<<" ";
cout<<endl;
for (i=0;i<=2000;i++)
flag[i]=0;
for (i=0;i<n-1;i++)
for (j=i+1;j<n;j++)
flag[a[i]+a[j]]=1;
cnt=0;
for (i=0;i<n;i++)
if (flag[a[i]]==1) cnt++;
cout<<"count="<<cnt<<endl;
return 0;
}
【例5】神奇算式
由4个不同的数字,组成的一个乘法算式,它们的乘积仍然由这4个数字组成。比如:210× 6 = 1260,8 × 473 = 3784,27×81 = 2187都符合要求。
如果满足乘法交换律的算式算作同一种情况,那么一共有多少种满足要求的算式呢?
(1)编程思路。
采用穷举法来解决。穷举对象分别为积和那个较小的乘数。其中积是一个4位数,取值范围为1023~9876,乘数的取值范围为2~98。
循环体中需要检测两个约束条件:1)积这个4位数中每一位数字不允许重复;2)这4个数字出现且仅出现2次。这些约束条件可使用一个标记数组used[10]来处理。used数组的初值全为0,当数字i使用后,置used[i]=1,若数字i再次使用时,此时used[i]又等于1,则可判定约束条件不满足。
(2)源程序及运行结果。
#include <iostream>
using namespace std;
int check(int x, int y,int used[10])
{
int i,tmp[10]={0};
do {
tmp[x % 10]++;
} while(x /= 10);
do {
tmp[y % 10]++;
} while(y /= 10);
for (i=0;i<10;i++)
if (tmp[i]!=used[i])
return 0;
return 1;
}
int check4(int x,int used[10])
{
do {
if(used[x % 10] != 0)
return 0;
used[x%10]++;
} while(x /= 10);
return 1;
}
int main()
{
int a,b,c,i,cnt=0,used[10];
for(c=1023; c <=9876; c++)
{
for (i=0;i<10;i++)
used[i]=0;
if(!check4(c,used))
continue;
for(a=2; a <= 98; a++)
{
if(c % a != 0)
continue;
b = c / a;
if(a > b)
continue;
if(!check(a, b,used))
continue;
cout << a << " * " << b << " = " << c << endl;
cnt++;
}
}
cout << "count="<<cnt<<endl;
return 0;
}
【例6】排它平方数
小明正看着 203879 这个数字发呆。
原来,203879 * 203879 = 41566646641
这有什么神奇呢?仔细观察,203879 是个6位数,并且它的每个数位上的数字都是不同的,并且它平方后的所有数位上都不出现组成它自身的数字。
请找出具有这样特点的所有6位数。
(1)编程思路。
对6位数进行穷举,区间下限为123456,上限为987654。
定义数组used[10]记录数字0~9的使用情况,分离出穷举的6位数的各位x,且置used[x]++,若每个used[x]大于2,则数字x重复出现,不合要求,进行下次穷举。
另外,由于两个6位数相乘结果超出了整型数的表示范围,采用两个数组元素来保存积。计算6位数i的平方时,将i表示为a*10000+b,这样i的平方等于a*a*100000000+2*a*b*10000+b*b,用数组元素x[1]保存低8位,用x[0]保存高8位,具体方法为:
x[1]=x[1]+c%10000*10000;
x[0]=x[0]+c/10000;
x[0]=x[0]+x[1]/100000000;
x[1]=x[1]%100000000;
然后再检测x[0]和x[1]中的各位数k是否已出现,即对应的used[k]是否为1。
(2)源程序及运行结果。
#include <iostream>
using namespace std;
int check(int x,int used[10])
{
do {
if(used[x%10] > 0)
return 0;
else
used[x % 10]++;
}while(x /= 10);
return 1;
}
int checkpower(int x[],int used[10])
{
do {
if(used[x[0]%10] > 0) {
return 0;
}
} while(x[0]/=10);
for (int i=1;i<=8;i++)
{
if(used[x[1]%10] > 0)
return 0;
x[1]=x[1]/10;
}
return 1;
}
int main()
{
int i,a,b,c,x[2];
int k,used[10];
for(i=123456; i<=987654; i++)
{
for (k=0;k<=9;k++)
used[k]=0;
if(!check(i,used))
continue;
a=i/10000; b=i%10000;
x[0]=a*a; x[1]=b*b;
c=2*a*b;
x[1]=x[1]+c%10000*10000;
x[0]=x[0]+c/10000;
x[0]=x[0]+x[1]/100000000;
x[1]=x[1]%100000000;
if(!checkpower(x,used))
continue;
cout << i << endl;
}
return 0;
}