比赛链接:https://vjudge.net/contest/763332。
A
最板子的双搜,首先算出总和 \(\sum_{i=1}^{n} i\) 并将其除以二,然后对半去搜就行了。
B
甚至可以直接爆搜,因为次数限制过小,不需要上双搜去辅助;当然也是可以用 map 记录字符串状态然后折半去搜索的。
C
同样也是非常板子的双搜。
D
哎你说得对啊,也是板子双搜啊,咋滴个嘞,我最后存到数组里去的是 \(x\) 不是 \(sum\) 啊!后头减的也是 \(x\) 不是 \(sum\) 啊!长个记性咯。
E
定义 \(f_{i}\) 表示存在多少组 \(x^2 + y^2 = i\),\((x,y)\) 为无序对,即 \((2,3)\) 和 \((3,2)\) 算作同一组。
这个可以最开始先 \(O(n^2)\) 地跑出来。
然后枚举最后的对角线长度 \(p\) 以及高度 \(x\),那么个数就是 \(f_{p^2-x^2}\),因为还要把剩下的部分分给长和宽嘛。
F
板子双搜,记得对 \(m\) 取模,以及最后的答案是 \(<m\) 不是 \(\le m\) 哦!取模后得到的值是不能超过或者等于模数的。
G
在二维矩阵上的双搜,也很简单的,这里用的是横纵坐标的值相加 \(x+y\) 判断步数情况,并且最后由于是把一个点重复走了两次所以要把 \(a_{x,y}\) 最后再异或回来一次不然就会算错了。
H
用双搜的话……感觉还好,但是代码实现好像特别复杂,因为你得不停地状压,然后又要压回来。暂时还没写。
不过看题解区怎么一大片的 A*、IDA* 啊?这是什么,迭代加深,启发式搜索,这是什么东西啊?不管了不管了哎不管了。
I
一开始看到图论一眼懵了,然后后面发现可以用二进制压缩存目前每个点的状态,然后变化就是一个异或的条件,每个点连的边的情况可以存下来,或者说是度吧。然后双搜一样的从 \(1\) 跑到 \(mid\) 再从 \(n\) 跑回 \(mid+1\),用 map 记录。
注意了这题它卡常或者是卡 map 或者是什么的,总之不能写递归,抽出来改成递推去枚举两部分分别的状压情况才能过。猎奇卡常题目。
J
这题它不是常规双搜只有两种选择(我呃呃也只有最基本的双搜只有两种选择了吧?),它一个奶牛可以选择放入第一组或者第二组,或者也可以干脆不纳入这个集合。当然嘛就要用一个 \(st\) 实时记录当前一二组总共的集合状态了,二进制压缩。存的时候每种 \(sum\) push_back 多个合法的 \(st\) 进去,对,用 map 套着 vector 记。然后后面找的时候枚举 vector 中所有元素,或在一起整。注意这里不要用 map 或者什么的存最后的集合情况然后直接查 .size(),会 T,直接用 bool 数组存下来最后枚举一遍算一下就行。
小结
双向搜索,即 meet-in-the-middle,是一个可以把 \(O(k^{n})\) 的算法优化成 \(O(k^{n \div 2})\) 的算法,可能会带上个把 \(\log\) 因为有时候会用 map 或者其他 STL 容器进行存储。时间复杂度中的 \(k\) 为一常量,通常不会超过 \(5\),常见为 \(2\) 或 \(3\)。
常见多为 \(n\) 在 \(30 \sim 60\) 左右的这个数据范围的情况下会用到双向搜索,字面意思嘛,和英文解释一样,就是“在中间相遇”。等同于就是,对半拆分,拆成左边 \([1,mid]\) 和右边 \([mid+1,n]\) 两段,然后分别从 \(1\) 跑到 \(mid\),以及从 \(n\) 跑到 \(mid+1\),然后中间相遇了,算出具体情况。通常为两次 DFS 实现左右边分别的情况,左边存右边算。有的时候题目时间需要可能会将递归改为递推用二进制状压来枚举具体情况。
总之,双向搜索是个不错的指数级别算法,它能把算法进行大幅度的时间降低(通常就是把指数砍半),是个很棒的算法哦!