埃及分数问题,对于任意分数 ab ,都可以写成 ∑1xi。其中xi互不相同。需要求出一个组合,其中项数最少。若项数一致,则需要最小的分数最大
思路分析: 理论上来说,回溯法可以暴力解决问题,但是解答数的项数太多,广度和深度都是无限的。对于稍微复杂一点的数据,将不能解决问题。
(1) 而迭代加深搜索算法则是限制每一次搜索的深度最大为maxd,然后进行搜索。这样每次加深一层,只要解的深度不是太深,最终就可以得到结果。
(2) 另外在进行这种暴力枚举的时候,需要注意一点。每一次的节点扩展只要深一层都可能让时间上深一个量级。所以要尽可能的限制深度。在这个题目的环境下,如果深度是maxd,那么对于最后一项我们就可以不需要继续枚举,而是直接检查是否符合埃及数的定义(需要用到gcd化简);此外,如果不想使用gcd进行化简,还有另外一种方法,那就是当 1xi已经不足以加到ab时,就停止循环。 但是这样有一点很容易错。这个检测只能在最后一层解答树执行,因为前面的节点是可以让 1xi小于ab的。
(3) 另外利用节点深度的限制,可以进行剪枝来优化算法。(提高极大的效率)由于深度固定,每一层的分数不可能小于最优值的最小值,否则最终不能加到原分数。而这样的情况则在前一种剪枝里面就i剪掉了。所以这里不能利用最优值优化。另外还有一个get_first函数的实现,每一层的枚举需要从接近 ab 的最大分数值开始。
def gcd(a,b):
return a if b==0 else gcd(b, a%b);
def get_first(a,b):
c = b//a
while c*a1
return c
def solve_2(a,b):
best = None
def better(data):
nonlocal best
if best==None:return True
else:
for i in range(maxd,-1,-1):
if data[i]!=best[i]:return data[i] < best[i]
return False
def dfs(a,b,beg,d):
nonlocal best
if d==maxd:
if b%a:return
data[d] = b//gcd(a,b)
if better(data):best = data[::]
return
i = max(beg,get_first(a,b))
while 1:
if (maxd-d+1)*b<=a*i:break
data[d] = i
dfs(a*i-b,b*i,i+1,d+1)
i+=1
for maxd in range(1,100):
best = None
data = [0]*(maxd+1)
dfs(a,b,get_first(a,b),0)
if best:print(best);break
solve_2(495,499)
Uva11212
(1)bfs搜索法
def solve(n,lines):
def bfs(data):
nonlocal states,q
q.append((data,0))
while len(q):
data,d = q.popleft()
if data==tuple(range(1,n+1)):return d
for i in range(1,n):
for length in range(1,continues(data,i)):
for j in range(i):
p1 = data[:j]
p2 = data[j:i]
p3 = data[i:i+length]
p4 = data[i+length:]
next = tuple(p1+p3+p2+p4)
if next not in states:
states.add(next)
q.append((next,d+1))
states = set()
q = deque()
print(bfs(lines))
(2): IDA*算法,有以下几个要点:
(1): 利用启发函数,考虑不正确后继为h个,那么每次剪切时h最多减少3。
(2): 考虑状态的判重,需要将重复的状态记录,当某一个状态重复的时候,需要看它对应的深度和当前深度来进行处理。
def solve_2(n,lines):
def count(data):
cnt=1
for i in range(n-1):
if data[i]+1!=data[i+1]:cnt+=1
return cnt
def bfs(data,d):
nonlocal states,ok
if d==maxd:
if data==tuple(range(1,n+1)):ok=True
return
for i in range(1,n):
for length in range(1,n+1-i):
for j in range(i):
p1 = data[:j]
p2 = data[j:i]
p3 = data[i:i+length]
p4 = data[i+length:]
next = tuple(p1+p3+p2+p4)
if next not in states or states[next]>d:
if 3*d+count(data)>3*maxd:continue
states[next]=d
bfs(next,d+1)
if ok:return
states = {}
for maxd in range(1,n):
states = {}
ok = False
bfs(lines,0)
print(maxd)
if ok:print(maxd);break
test(solve_2,[8,[8,7,6,5,4,3,2,1]])
利用双向bfs来优化代码: 很容易发现由于迭代加深搜索前几次搜索速度非常的快。但是后面每一次的复杂度都急速上升。所以我们可以借鉴一下双向bfs的思路,从目标节点开始进行bfs,将第k(1,2,3..)层的节点存储起来,然后再进行IDA,这样就将迭代的深度限制在最多maxd-k层。经测试算法效率提高了几十倍。(对987654321从22s提高到0,77s.)
同理,利用双向bfs也可以大大加快直接bfs的速度。当然双向bfs略有不同,它是正反交替搜索的,这样的结果是深度被限制在 maxd2 。
def solve_2(n,lines):
def count(data):
cnt=1
for i in range(n-1):
if data[i]+1!=data[i+1]:cnt+=1
return cnt
def bfs(data,d,record=None):
nonlocal states,ok,Record
if d==maxd:
if data==tuple(range(1,n+1)):ok=True;print('aa',maxd,data);return
if data in Record:ok = 3;print('ss',maxd,data)
if record!=None:record.add(data)
return
for i in range(1,n):
for length in range(1,n+1-i):
for j in range(i):
p1 = data[:j]
p2 = data[j:i]
p3 = data[i:i+length]
p4 = data[i+length:]
next = tuple(p1+p3+p2+p4)
if next not in states or states[next]>d:
if not record and 3*d+count(data)>3*(maxd+2):continue
states[next]=d
bfs(next,d+1,record)
if ok and record==None:return
states = {}
Record=set()
maxd,ok=2,False
bfs(list(range(1,n+1)),0,record=Record)
for maxd in range(1,n):
states = {}
ok = False
bfs(lines,0)
if ok:print(maxd+ok-1);break