code500这道题其实并不难,只是由于自己太错误的执着,导致花了不少时间。
题目描述如下:
女神Alice找回了钱钱,女神非常开心,然后就去洗澡了 这时Mallory已经逃走了! plusplus7希望抓住Mallory,于是决定开挖掘机去追他。 刚刚坐上挖掘机,结果发现启动挖掘机需要把一些插头按照一定的顺序连接起来... 插头有两面,每个插头的两端都有颜色 如: 绿色和黑色插头 “G:B” 当插头的两个面颜色相同,才能被接在一起 如: 对接成功 “G:B = B:V” 对接失败 “G:B * C:G” 插座也有两面,一面有颜色,一面无颜色以"X"表示 如: 左插座 “X:B” 右插座 “C:X” 插座只有两个,且不可移动 如: “X:B * C:G = G:E * B:C * E:X” 对挖掘机的siri输入指令,“3 3 1” “X:B = B:C = C:G = G:E = E:X” 那么,问题来了... 218.2.197.248:7777
第一眼看到题目的时候,确实是被那个指令给搞懵了,但是稍微恢复了下神智之后,到nc上去试,试了半天总算猜对了指令的含义:
a b c 表示将第a到b个插头移动到第c个插头的前面(这里也将插座算入,编号从0开始)
弄明白指令的含义之后,说实在的,对于该怎么做其实还是没有什么好的想法(因为操作有步数限制,要在规定步数内完成才算有效)。
但是观察nc看到的数据发现,似乎所有的字母永远都只出现2次,即都是唯一匹配的,然后当时脑子里很快就有了个猜测:
对于两个相邻的未匹配的插头,假设编号为a – 1和a,left[i]、right[i]分别表示第i个插头的两个面的颜色,那么,一定存在b、c,使得left[a] = right[c]、right[a – 1] = left[b],如果b、c满足b < c且[a – 1, a]与[b, c]不相交,那么我们这一步就将[b, c]这一段移动到a – 1与a中间,即指令为b c a。
这个猜想很好理解,至于这样做是不是就是最好的抉择没太想清楚怎么证明,但感觉是可行的。
然后,对于不满足这个猜想的条件的,我最开始是采取随便取一个的方式,结果花了很多时间也没能解决,经常会比要求的步数多1步,最多好像是能跑到30多轮吧,但是看了下code200的数据组数,50组,于是觉得这题也是这个数,于是就对跑不出来的特例暴搜得到答案后硬编码判断,然后就成功的跑到了68轮,感觉已经无法再爱了……
最后,无奈,重写了一下搜索的代码,加上上面的那个猜测作为A*,同时限制了一下,保证每次移动都不会将两个已经配对的插头分开,然后就很轻松就秒了,最后结果有92轮……
#!/usr/bin/env python #encoding:utf-8 import socket def get_one(s): try: ret = s.recv(100) while ret.find(":Xn") == -1 and ret.find("SCTF") == -1: ret += s.recv(100) print ret return ret[:-1] except Exception, e: print 'Fail', ret return ret def check(now, size): for i in xrange(1, size): if now[i - 1][2] != now[i][0]: return False return True def swap(now, i, j, k, deep): history[deep] = [i, j, k] if k < i: return now[:k] + now[i:j + 1] + now[k:i] + now[j + 1:] elif k > j: return now[:i] + now[j + 1:k] + now[i:j + 1] + now[k:] return now def dfs(now, deep, max_deep, size): if deep == max_deep: if check(now, size): return True return False for i in xrange(1, size): if now[i - 1][2] != now[i][0]: for j in xrange(1, size): if now[i - 1][2] == now[j][0]: start = j break for j in xrange(0, size - 1): if now[i][0] == now[j][2]: stop = j break if start <= stop and (start > i or stop < i - 1): return dfs(swap(now, start, stop, i, deep), deep + 1, max_deep, size) for i in xrange(1, size - 1): if now[i - 1][2] == now[i][0]: continue for j in xrange(i, size - 1): if now[j][2] == now[j + 1][0]: continue for k in xrange(1, i): if now[k - 1][2] != now[k][0] and dfs(swap(now, i, j, k, deep), deep + 1, max_deep, size): return True for k in xrange(j + 2, size): if now[k - 1][2] != now[k][0] and dfs(swap(now, i, j, k, deep), deep + 1, max_deep, size): return True return False def get_answer(now, max_step): now = now.replace(' ', '').replace('=', '*').split('*') size = len(now) for i in xrange(1, max_step + 1): if dfs(now, 0, i, size): print history return i history = [[] for i in range(10)] s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('218.2.197.248', 7777)) round_count = 1 ret = get_one(s)[len("Let's start this game!n"):] while True: step = get_answer(ret, 7) for i in xrange(step): s.send("%d %d %d" % (history[i][0], history[i][1], history[i][2])) ret = get_one(s) print 'Round', round_count, 'finsh.n' round_count += 1 ret = get_one(s)[len('Round pass...n'):]
重写代码的时间比之前小调优化的时间少了不知道多少倍,感觉大好的时光就这么浪费了,还好最后pwn400轻松秒了,不然估计真得为这个事情后悔好久~~~
关于代码写的丑的问题,各位看官就不用吐槽了,毕竟当时大脑已经是一半浆糊了,只要能跑出来,哪还有心情管代码美不美,直接在暴力上面改的,所以DFS都没有改成BFS,强行枚举了max deep,我也是服了我自己的……
这道题最后比赛结束之后竟然还被打电话challenge了,被质疑速度,看起来似乎标答应该不是搜索的样子,也不知道有没有哪位会愿意把标准解法分享一下了~~~