穷举及其优化

合集下载
相关主题
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

1 3 5
9 8 7
2 4 6
分析:本题目有9个格子,要求填数,如果不考虑 问题给出的条件,共有9!=362880种方案,在 这些方案中符合问题条件的即为解。因此可以采用 穷举法。 但仔细分析问题,显然第一行的数不会超过400, 实际上只要确定第一行的数就可以根据条件算出其 他两行的数了。这样仅需穷举400次。
例3、除法运算(NOIP1995初中组复赛第一题) 设有下列的除法算式:
请根据上述算式中的信息求出被除数和除数。
分析:设除数为x,被除数为y,由算式信息可知: 10<=x<=99,1000<=y<=9999,且8*x<=99, 9*x>=100。因此,我们可选择枚举除数,而被除数 则可按公式y=809*x+1计算得出。
例2、选数:已知n(1<=n<=20)个整数x1,x2,…,xn (1<=xi<=5000000),以及一个整数k(k<n)。从n 个整数中任选k个整数相加,可分别得到一系列的和。 例如当n=4,k=3,4个整数分别为3,7,12,19时, 可得到的全部组合及它们的和为 3+7+12=22,3+7+19=29,7+12+19=38,3+12+19=34。 现在,要求你计算出和为素数的组合共有多少种。 如上例中,只有一种组合的和为素数:3+7+19=29。 输入: 样例 n , k 输入: x1,x2,…,xn 43 输出: 一个整数(满足条件的组合个数) 3 7 12 19 输出: 1
var x,y:integer; begin for x:=10 to 99 do {枚举除数x,范围是 10~99} begin y:=809*x+1; {计算出被除数y} if y>9999 then break; if (y>=1000) and (8*x<=99) and (9*x>=100) then writeln(y,' ',x);{验证,如果 满足要求,则输出} end; end.
B8
优化思路:分解约束条件,将拆分的约束条 件嵌套在相应的循环体间,尽可能减少可行解 的数目,也称为“剪枝”,即把明显不符合条 件的可行解尽可能地剪去,减少穷举的计算量。
六、实例分析:
例7、巧妙填数。 问题描述:将1~9这九个数字填入九个空格中。 每一横行的三个数字组成一个三位数。如果要使第 二行的三位数是第一行的两倍,第三行的三位数是 第一行的三倍,应怎样填数。如图所示。
如何优化穷举算法
提高穷举效率的方法:
1、根据问题的实际需要,将反复操 作部分预处理掉; 2、根据问题的实际的条件实施剪枝 处理。
例4、阿姆斯特朗数。 问题描述:编一个程序找出所有的三位数到七位数 中的阿姆斯特朗数。 阿姆斯特朗数也叫水仙花数, 它的定义如下:若一个n位自然数的各位数字的n次 方之和等于它本身,则称这个自然数为阿姆斯特朗数。
生成法求组合
for i:=0 to k do a[i]:=i; {产生第一种组合} while a[0]=0 do begin sum:=0; { sum是累加器,求当前组合的k个整 数之和} for i:=1 to k do sum:=sum+x[a[i]]; if fit(sum) then total:=total+1; {验证当前组合的 k个整数之和是否为素数} j:=k; while a[j]=n-k+j do j:=j-1; a[j]:=a[j]+1; {产生一种新的组合} for i:=j+1 to k do a[i]:=a[i-1]+1; end;
如何来优化算法呢?
如果注意到b3和b6两个格子,与它们“相邻”的格子 有6个,也就是说,放入这两个格子中的数,必须和6个 数不连续,仅可以和一个数是连续的,这样的数只有2个, 即1和8。这样,b1,b3,b6,b8;4个格子中数的放 法仅有两种可能:2、8、1、7和7、1、8、2。而b2、 b4、b5、b7四个格子中的数仅需在3、4、5、6四个 数中选择。经过上述优化,可行解仅有:2×4!=48个, 大大减少了计算量。并且检验是否符合要求, 也只需检查(b1,b2), B1 (b1,b4),(b2,b5), B2 B3 B4 (b4,b7),(b5,b8), B5 B6 B7 (b7,b8)这6对数之差就可以了。
a[0] 0
0 0 0
a[1], 1
1 1 2
a[2] 2
2 3 3
a[3] 3
4 4 4
生成的组 合 3
3 3
7
7
12
19
1
2
3
4
12 19 7 12 19 循环结束
② 判断一个整数是否为素数 判断一个整数P(P>1)是否为素数最简单的方法就是 看是否存在一个素数a(a<=sqrt(P))是P的约数,如果 不存在,该数就为素数。由于在此题中 1<=xi<=5000000,n<=20,所以要判断的数P不会超 过100000000,sqrt(p)<=10000,因此,为了加快速 度,我们可以用筛选法先将2…10000之间的素数保 存到一个数组里(共1229个),这样速度估计将提 高许多。
sum:=sum+power[digit[i],highest]; if sum=currentnumber
then begin total:=total+1;
write(currentnumber:maxlen+5); if total mod 6=0 then writeln end;
digit[1]:=digit[1]+1;
程序: const maxlen=7; var i,j,currentnumber,highest,sum,total:longint; digit:array [0..maxlen+1] of integer; power:array [0..9,0..maxlen] of longint; begin for i:=0 to 9 do begin power[i,0]:=1; for j:=1 to maxlen do power[i,j]:=power[i,j-1]*i end;
LINK 1 1 2 1 3 1 4 2 5 2 6 2 7 3 …… 13 6 14 7
2 3 4 3 5 6 4 7 8
表中表示哪两个格子是相邻的,link[i,1]和 link[i,2]是相邻的格子的编号。全排列的产生,可 以用八重循环,也可以用构造的算法,程序留给同学 们自己去完成。利用穷举策略编制的程序,其运算量 一般是很大的,因此如何提高算法效率是穷举算法一 个很重要的问题。一般应尽量减少可行解的个数,使 得第二步的检验运算量尽可能地少。
begin
i:=1;
while digit[i]=10 do digit[i+1]:=digit[i+1]+1; digit[i]:=0; i:=i+1 end;
进位
if i>highest then highest:=i;
currentnumber:=currentnumber+1 end;
writeln
分析:本题可分解成以下两部分: 从n个数中任取k个数的组合 因为n<=20,所以可以用穷举实现。用数组 a[1],a[2],…,a[k]记录每种组合中各数的下 标,a[0]是循环开关,初始时a[0]=0,当 a[0]=1时穷举结束。当选用前面的输入样 例,n=4,k=3时,a[0]~a[3]的变化如下表 所示:
穷举及其优化
泰州二附中 谢志锋
例1、一个火星人用一个人类的手演示了如何用 手指计数。如果把五根手指——拇指、食指、中指、 无名指和小指分别编号为1,2,3,4和5,当它们按 正常顺序排列时,形成了5位数12345,当你交换无 名指和小指的位置时,会形成5位数12354,当你把 五个手指的顺序完全颠倒时,会形成54321,在所有 能够形成的120个5位数中,12345最小,它表示1; 12354第二小,它表示2;54321最大,它表示120。 下表展示了只有3根手指时能够形成的6个3位数和它 们代表的数字: 三进制数 123 132 213 231 312 321 代表的数字 1 2 3 4 5 6
for i:=1 to maxlen do digit[i]:=0; digit[3]:=1; highest:=3; currentnumber:=100; total:=0;
while digit[maxlen+1]=0 do
begin sum:=0;
for i:=1 to highest do
分析:枚举对象为x和y,范围如何? 仔细观察1/12=1/156+1/13 可以看 出来,x可以比y大很多。Y是有一定范围的。
通过不等式变形:
1 1 1 1 1 x y,有 ,因此 ,即y 2k x y k y y
显然,Hale Waihona Puke Baidu需在2k范围内枚举y,然后 根据y尝试计算出x即可。
例6、方格填数。 问题描述:如图所示的8个格子中放入1~ 8八个数字,使得相邻的和对角线的数字之 差不为1。编程找出所有放法。
B1 B2 B5 B3 B6 B8 B4 B7
分析:我们先不考虑后一条件, 只考虑第一个条件,即把1~8八 个数字放入8个格子中。这是容易 做到的,就是8个数字的全排列, 共有8!=40320种放法。然后对 这8!个可行解用后一个条件加以 检验,输出符合条件的解。对于 后一个条件中“相邻”的判断, 可以建立一个邻接表来解决:
end.
例5:分数拆分 输入正整数k,找到所有的正整数x≥y, 使得 1 1 1
k x y
样例输入: 2 12 样例输出 2 1/2=1/6+1/3 1/2=1/4+1/4
12 1/12=1/156+1/13 1/12=1/84+1/14 1/12=1/60+1/15 1/12=1/48+1/16 1/12=1/36+1/18 1/12=1/30+1/20 1/12=1/28+1/21 1/12=1/24+1/24
min:=a[i];mini:=i; end; temp:=a[mini]; a[mini]:=a[j-1]; a[j-1]:=temp; for x:=j to n-1 do for y:=j+1 to n do if a[x]>a[y] then begin temp:=a[x]; a[x]:=a[y]; a[y]:=temp; end; t:=t+1; if t=m then break; end;
现在你有幸成为了第一个和火星人交流的地球人。 一个火星人会让你看他的手指,科学家会告诉你要 加上去的很小的数。你的任务是,把火星人用手指 表示的数与科学家告诉你的数相加,并根据相加的 结果改变火星人手指的排列顺序。输入数据保证这 个结果不会超出火星人手指能表示的范围。 【样例输入】 5 3 12345 【样例输出】 12453 【数据规模】 对于30%的数据,N<=15; 对于60%的数据,N<=50; 对于全部的数据,N<=10000;
问题分析
直接穷举,因为数据较大显然不能达到 目的,由于本题本质上是求全排列,这里用 生成法求全排列比较合适。
read(n); read(m); for i:=1 to n do read(a[i]); t:=0; a[0]:=0; while a[0]=0 do begin j:=n; while a[j]<a[j-1] do j:=j-1; mini:=j;min:=a[j]; for i:=j to n do if (a[i]>a[j-1]) and (a[i]<min) the begin
例 如 153(153=1*1*1+3*3*3+5*5*5) 是一个三位数的阿姆斯特朗数,8208则是一个四位 数的阿姆斯特朗数。
算法分析:
为了使得程序尽快运行出正确结果,程序中使 用了一个数组power存放所有数字的各次幂之值, power[i,j] 等 于 i 的 j 次 方 。 变 量 currentnumber存放当前要被验证的数,数组 digit 存 放 当 前 数 的 各 位 数 字 , 开 始 时 digit[3]=1,其它元素均为0,此时表示当前数为 100。 highest为当前数的位数。
相关文档
最新文档