难度:β-
建议用时:30 min
实际用时:40 min
题目:??
代码:??
这题是我目前做的最快的一题。
因为我看了别人的题解。
代码照着别人抄的,当然做的快。
我现在有些不好意思了。
现在只是在别人的基础上说些我的理解。
这题用 DFS 很好说。然而剪枝我开始没想到。事后想一想也有道理。
因为这题设计到了长度拼凑问题,根据经验,这类题要用剪枝。像 Egyptian Fractions 这种题目就是列子。
那么,下面就是确定搜索状态的时候了。
我因为很水,想了半天不知道怎样用搜索状态。然后一看题解就懂了。
状态有 3 个变量,一个存目前已经拼凑完整(相对迭代深搜限定的长度)的木棍组数,一个存当前枚举到的木棍编号,
一个存目前要拼凑的长度已经累积到多少了。
好吧,这三个变量就是这题搜索需要的所有信息了。当然还有每根木棍的长度。等等。
这个搜索状态的寻找,我只能说凭经验了。
下面就是搜索了。
先给一个伪码吧。
1 bool solve(int cnt, int cur, int len) { 2 if (cnt * lth == sum) return true; 3 4 for (int i = cur; i < n; i++) { 5 if (lenth[i] + len < lth) { 6 vis[i] = 1; 7 if (solve(cnt, i+1, len+lenth[i])) return true; 8 vis[i] = 0; 9 } 10 11 if (lenth[i] + len == lth) { 12 vis[i] = 1; 13 if (solve(cnt+1, 0, 0)) return true; 14 vis[i] = 0; 15 } 16 } 17 18 return false; 19 }
这是想得到正确答案的必要步骤。
算法是:
1)如果当前状态下已经把所有木棍凑整了,那么显然找到解了,返回
2)枚举每一木棍,如果用过了,跳过。
3)如果没有用过,尝试把这根木棍加到目前累计的长度上。如果加上后的长度比目标的小,就尝试继续加。
4)否则,如果刚好就加到了目标长度,尝试进行下一组的拼凑。
在整个过程中,牵涉到了 vis 数组的使用。一根木棍不能用两次。
(这里先事先提醒一下,我们枚举木棍时,是一遍一遍从长木棍枚举到短木棍(之前读入后排个序),在没有凑完整一组之前,我们是不会往回枚举的。
了解这一点可以更容易理解下面剪枝的方案。)
这个算法足以得到正确答案了。(测的是样例)
然而,如果不适当剪枝,100 ms 瞬间飙到 860 ms。不过还是可以 AC 的。
不管怎样,上面提到的那位大神想到了 3 个剪枝办法。
1)最最重要的,如果当前的木棍已经用过了,或是上一根木棍的长度和当前木棍的长度相等,然而上一根没用,那么这一根也不可能会用到。跳过。
if (vis[i] || (i && lenth[i] == lenth[i-1] && !vis[i-1])) continue;
为什么呢?因为上面提到的,我们枚举木棍是从大到小递减的(不是严格的),如果排在之前的木棍和自己的长度一摸一样,然而没选它,怎么可能选自己?
如果不选它而选自己,那么后面搜索时就一定会有使自己凑整的方案。
然而,作为大哥,怎么能让小弟抢走自己的份额?大哥一定会先抢走,对吧?所以大哥没有的,小弟怎么会有?
(我不会告诉你我是从我的化学老师那里学来的江湖口吻)(~~)
2)次重要的,如果当前处理的是第一根木棍,然而这一根选了后没有解,那么不管怎么选后面的,这一根是不会被选的,与他同长度的也不会被选。直接跳过,返回。
1 if (len == 0) return false;
3)次次重要的,如果当前选择的木棍刚好可以凑整了,然而下一组和下下一组和下下下一组的木棍不知怎么就凑不整了,那么也可以返回。
return false;
(在这里放代码好像没什么用)(没有上下文啊!)
这个剪枝我还没想好怎样证明。放这里,打个记号 ??
好了,总之借助于其他大神的剪枝方案,我把这题还是水过去了。
说实话,这题剪枝还是有些不寻常的。(就目前我的水平来说)
2018-01-29