SCTF Code500 WriteUp

Reading time ~1 minute

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了,被质疑速度,看起来似乎标答应该不是搜索的样子,也不知道有没有哪位会愿意把标准解法分享一下了~~~

挂载网络文件夹后网络故障时文件操作命令卡死

挂载 NFS 或者 Samba 的时候,经常会由于网络故障导致挂载好的链接断掉。此时如果尝试进行 ls、cd、df 等各种命令,只要与此目录沾上边,就会卡住。如果使用了类似 oh-my-zsh 这种配置的,只要在网络目录中,弹出命令提示符前就会直接卡住。这个时候第一反应就是...… Continue reading

路由折腾记 第四弹

Published on September 02, 2017