您的位置:首页 > 其它

郑州集训day2下午:搜索基础

2017-10-04 20:17 211 查看

搜索

简介

搜索算法是利用计算机的高性能来有目的的穷举一个问题解空间的部分或所有的可能情况,从而求出问题的解的一种方法。

现阶段一般有枚举算法、深度优先搜索、广度优先搜索、A* 算法、回溯算法、蒙特卡洛树搜索、散列函数等算法。

在大规模实验环境中,通常通过在搜索前,根据条件降低搜索规模;根据问题的约束条件进行剪枝;利用搜索过程中的中间解,避免重复计算这几种方法进行优化。

dfs/bfs

dfs即深度优先搜索,bfs即广度优先搜索

伪代码

dfs:

void DFS(int x) {
vis[x] = true;
// do sth;
for (int to : g[x]) {
if (!vis[to]) {
DFS(to);
// do sth
}
}
// do sth
}


bfs:

void BFS(int s) {
queue<int> Q;
Q.push(s);
vis[s] = true;
while (!Q.empty()) {
int u = Q.front();
Q.pop();
// Do sth
for (int to : g[u]) {
if (!vis[to]) {
vis[to] = true;
Q.push(to);
// Do sth
}
}
}
}


经典例题

1)文本编辑

使用两个队列来维护状态

》》线性算法

利用图的思想。

边权只有1或2,用两个队列Q1,Q2。Q1存距离远点为偶数的点,Q2存距离原点为奇数的点。从小的开始扩展。

2)NOIP2016斗地主给定斗地主的一组手牌,问用合法规则将它们全部打完,最少需要多少轮

多组询问

N <= 23

考虑预处理 dp[x][y][z][w] 表示分别有 x、y、z、w 种出现 4、3、2、1 次的牌,不出顺子最少几步打完

DFS 搜索打顺子的情况,结合 dp 计算答案

最优性剪枝

Meet in middle 折半搜索法

经典例题

1)方程的解数

已知N元高次方程,解<=150,n<=6

如果我们暴力搜解的话那么我们要搜的是:



那么显然很容易TLE

这时候就可以用到我们的折半搜索法了。

将其分为两部分,搜索的前半部分放进Hash表,然后再搞就可以大大优化时间复杂度

2)离散对数

解方程A^X=B(mod m)

给定M,A,B

设步长 m

那么任意一个解可以写成 x = km + r

那么 A^{r} = A^{-km} * B (mod M)

枚举可能的 r,把左半部分扔进 Hash 表

枚举可能的 m,尝试寻找解

复杂度 O(m) + O(M / m) >= O(\sqrt{M})

3)简单题

给定 4 个长度为 N 的整数数组,给定 M

求:从 4 个数组中分别选一个数 a, b, c, d,使得

abcd=1 (mod M)

的方案数

N <= 4000

那这题就是abcd mod M=1

这题还是类似的思想,将四重搜索分开,枚举ab的乘积放入Hash表,则ab与cd互为乘积逆元,再求解就很方便了。

这样子时间复杂度就优化到了O(n^2*logm)。

启发式搜索

目的:利用问题拥有的启发信息来引导搜索,减少搜索范围,降低问题复杂度。

原理

A* 和 IDA* 算法中,对每一个状态 x 引入了一个估价函数 f (x) = g(x) + h(x), 其中 g(x) 是目前状态的实际代价,而 h(x) 是 “目前状态到目标状态的估计代价”。

在 A* 算法中,估价函数的作用是调整搜索顺序让最优的解最 先搜索到。

而在 IDA* 中,估价函数作为最优性剪枝出现。

A* (堆)

是对BFS的改进,用优先队列维护所有可扩展的状态

每次把期望里目标更近的状态拿出来扩展。

IDA*

从小到大“试答案”,判定一般使用深搜(当深度>ans就不必搜下去了)

比较:

A*:容易理解,容易实现,空间需求巨大

IDA*:不太容易实现,空间需求小,但是有重复扩展问题(PS:因为一般搜索树第X层大小远大于x-1层,一般认为IDA*的在找到答案之前的重复扩展都“微不足道”)

代码:

bool dfs(int x, int cost) {
if (x == object) return true;
if (cost + h(x) > limit) return 0;
for (int y : g[x]) {
if (dfs(y, cost + len(x->y))) {
return 1;
}
}
return 0;
}


bool check(int v) {
limit = v;
dfs(s);
}


int solve()

{

int ans=0

while(!check(ans)) ans++;///判断

}

int solve() {
int ans = 0;
while (!check(ans)) {
ans++;
}
}


经典例题

(1)8数码



这是我们要达成的目标状态

对于任意的初始状态,求到达目标状态的最少步数。

》》》15数码

15数码由于图比较大,用 A* 扩展会有严重的空间问题,只能用 IDA* 解决。

但是事实上效率还不错

(2)骑士精神

在一个 5 * 5 的棋盘上有 12 个白骑士和12 个黑骑士

每次可以选择一个骑士合法地走到空地

给定初始和目标状态,问最少走多少步能到达目标状态

(同种颜色的骑士是不互相区分的)

双向搜索可以接受

h(X)可以采用未完成棋子的个数

Dancing Links

很牛逼然而我们并不讲 gg自己找(有兴趣)

总经典例题:

(1)一个二维数组大小不过250

选择若干行、列,分数是这些行和列交叉点的分数之和,选择适合的行和列,最大化你的得分。

因为R*C=250

min(R,C)<=15

所以对较小数进行暴力搜索,较大数用贪心

(2)给定一个 DAG,每个点有点权;你需要把点分为若干组,满足:

每组的点权和 <= M

存在一个拓扑序使得每组中的点连续

请最小化组数

M <= 100
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: