Loading

搜索

剪枝

剪枝分为两种:可行性剪枝和最优化剪枝。

可行性剪枝

如果当前状态明显不可行,就直接返回。譬如说网格图搜索上对于边界的判断。

最优性剪枝

如果当前的最优状态并没有当前的答案优秀,就也不用继续搜索了。

双向广搜

双向同时搜索

定义

双向同时搜索的基本思路是从状态图上的起点和终点同时开始进行广搜或深搜。

如果发现搜索的两端相遇了,那么可以认为是获得了可行解。

Meet in the middle

过程

Meet in the middle 算法的主要思想是将整个搜索过程分成两半,分别搜索,最后将两半的结果进行合并。

性质

暴力搜索的时间复杂度往往是指数级别的,Meet in the middle 可以让指数减半,也就是从 \(O(a ^ b)\) 降到 \(O(a ^ {b / 2})\)

洛谷 P2962

题意

有一张 \(n\) 个点,\(m\) 条边的无向图,最开始每个点的状态都是 \(0\)

每操作一个点都会使得这个点以及与它相邻的点的状态取反。

请问最少需要多少次操作使得这 \(n\) 个点的状态都变成 \(1\)

\(1 \le n \le 35, 1 \le m \le 595\)

思路

由于 \(n\) 只有 \(35\),所以,可以考虑搜索。但是,时间复杂度为 \(O(2 ^ n)\),会超时,所以,我们考虑 Meet in the middle(一般 \(n\) 的范围是 \(30 \sim 40\) 都可以考虑 Meet in the middle)。

所以,我们只需要在第一次搜索时记录下这 \(n\) 个点的状态所对应的最小操作数,在第二次操作时求出对应的第一次搜索后的状态,取最小值即可。

代码

void dfs(int t, int c, bool p, int cnt) {
	if (c == l[p] + 1) {
		ll tmp = 0;
		for (int i = n; i >= 1; i--) tmp = tmp * 2 + f[i];
		if (!p) {
			if (!mp.count(tmp)) mp[tmp] = cnt;
			else mp[tmp] = min(mp[tmp], cnt);	
		} else {
			tmp ^= (1ll << n) - 1;
			if (mp.count(tmp)) ans = min(ans, cnt + mp[tmp]);
		}
		return ;
	}
	dfs(t + D[p], c + 1, p, cnt);
	
	for (int i : g[t]) f[i] ^= 1;
	
	f[t] ^= 1, dfs(t + D[p], c + 1, p, cnt + 1), f[t] ^= 1;
	
	for (int i : g[t]) f[i] ^= 1;
}

A*

定义

A* 搜索算法是一种在图形平面上,对于有多个节点的路径求出最低通过成本的算法。它属于图遍历和最佳优先搜索算法。

过程

定义起点 \(s\),终点 \(t\),从起点(初始状态)开始的距离函数 \(g(x)\),到终点(最终状态)的距离函数 \(h(x)\)\(h^*(x)\),以及每个点的估价函数 \(f(x) = g(x) + h(x)\)

A* 算法每次从优先队列中取出一个 \(f\) 最小的元素,然后更新相邻的状态。

如果 \(h \le h^*\),则 A * 算法能找到最优解。

IDDFS

迭代加深是一种 每次限制搜索深度的 深度优先搜索。

迭代加深本质上还是深搜,但是带上了一个深度 \(d\),当 \(d\) 到达了设置好的深度就返回,一般用于寻找最优解。

如果这一次搜索并没有找到最优解,就把设置的深度 \(+ 1\),重新从根开始找。

事实上,迭代加深就类似于用 DFS 方式实现的 BFS,它的空间复杂度相对较小。

void IDDFS(int t, int d) {
  if (d > dep) return ;
  for (int i : g[t]) IDDFS(i, d + 1);
}

IDA*

定义

IDA * 是用估价函数进行剪枝的 IDDFS。

  1. 不需要判重,不需要排序,利于深度剪枝。

  2. 空间需求减少:每个深度下实际上是一个深度优先搜索,不过深度有限制,使用 DFS 可以减小空间消耗。

  3. 重复搜索:即使前后两次搜索相差微小,回溯过程中每次深度变大都要再次从头搜索。

posted @ 2023-10-07 14:23  Yan719  阅读(51)  评论(0)    收藏  举报