一、搜索
•搜索算法在OI中有着重要的作用
•解题
•暴力骗分
•对拍
•广度优先搜索
•深度优先搜索
•A*算法
•迭代加深搜索
•蒙特卡洛树搜索算法(AlphaGo)
(1)广度优先搜索
•Breadth First Search
•从一个状态开始同时扩展出数个后续状态,后续状态再继续扩展。
•核心用处:求解最短步数、图的遍历
•实现:队列
•优点:搜到的第一个可行解一定是最优解
•缺点:时间、空间消耗大
•算法核心:去重和记录状态
•在广搜中,去重是必要步骤,保证广搜的算法复杂度不会退化。而记录状态的方法则关乎到程序的时间复杂度、空间复杂度、代码复杂度(调试是否方便)。
•一个点扩展出多个节点,扩展顺序成树形的辐射状,新增节点数以指数级增长。
•层数越大,节点越多。到终点的最短路径只有一条,大部分节点是无用的节点。
双向广搜
•还是广搜的框架,同时从起点和终点开始搜索,搜到中间交会点时停止。
•有效地减少了无用点。
•注意区分重复状态和相遇状态
A*算法
•A*(A-Star)算法是一种静态路网中求解最短路径最有效的直接搜索方法,也是解决许多搜索问题的有效算法。算法中的距离估算值与实际值越接近,最终搜索速度越快。
•如何设计距离估算值是重点
•OI中(尤其是NOIP)不常用,作为扩展知识
•第k短路 POJ2449
•广搜占用空间和时间都很大,要优化常数,代码不能写太挫。
•去重方法:bool数组,hash,map(*)
•赛场上时间有限,选择合理的算法得到更多的分数。
(2)深度优先搜索
•Depth First Search
•递归实现,栈
•从一个状态开始一直往下搜搜到尽头,然后回溯。
•优点:占用空间小,深度大
•缺点:搜索范围大,不易找到最优解,大多数时候时间复杂度是指数级的。
可行性剪枝
•深搜的过程就是一个不断寻找更优解的过程,然而,有些分枝根本不可能得到一个解,这时候我们就需要把这些枝条“剪掉”,这也称为可行性剪枝。可行性剪枝把“不可行”的剪掉,所以提供的是一个必要条件。
•回到题目中,一个最基本的限制就是wi的和不能超过W。
排序
•我们都喜欢有序的东西,不喜欢无序的,因为有序总比无序要多一些性质可以利用。搜索过程也是一样。
•有几种排序的方法:1、体积从大到小排序,以更好地触发可行性剪枝。2、价值与体积的比值从大到小排序,以获得更大的单位价值。
最优性剪枝
•在剪枝里面,最优性剪枝最为重要,也是最难想、最多样的部分。一个好的最优性剪枝能对程序有着非常大的提升。
•之前提到,深搜的过程是不断寻找更优解的过程,那么一个判断解是否足够优的条件就是是否优于当前解。比当前已有的解劣的都是不够优的,应当剪掉。
•假设当前最优解的解为V,当前步骤搜索到的价值和为V’,若V’+后面所有草药的价值<V,则一定搜不到更优解
•正确性显然
•结合前面的剪枝,可以顺利通过这题。
迭代加深搜索与广搜
•层层递进的方法确实与广搜非常相像,但是迭代加深有个缺点,就是会有重复搜索的部分。但是迭代加深搜索的算法复杂度仍然是正确的呢?
•回忆一下广搜的辐射形搜索。
•层数越大,枝数越多,浅层的搜索相比之下只占很小一部分。
•迭代加深搜索是广搜与深搜思想的结合
•优点:层数可控(与广搜类似),空间非常小(深搜的优点)
•常用的深搜剪枝技巧:可行性剪枝、排序、最优性剪枝、重复性剪枝、利用初始条件……
•注意:一定要保证剪枝正确,一个跑得慢的程序在OI中要优于一个错误的程序。
•挖掘题目信息,注意数据范围,寻找剪枝的切入点。
•一个好的骗分暴搜对你的成绩有很大帮助
•多练
二、分治
•分治即“分而治之”。当一个问题过于复杂无法处理时,将这个问题分解成两个或多个同类的子问题,通过解决子问题来求出原问题的解。
•最简单的例子:二分,快排,归并排序
分治的步骤:
1.分解。将一个大问题分解为若干个(对数情况下为两个)子问题求解。
2.解决。子问题分解到一定程度即可得出答案。
3.合并。将子问题的答案合并起来,得到原问题的答案。
分治能够实现的条件:
n问题被分解到足够小时能够解决
n对于一个问题,能够分为几个互不影响小问题分开解决
n子问题的解可以合并为一个问题的解
平面最近点对
•问题描述:在一个平面中给出n个点,求这些点中最近的一对
•直接暴力枚举O(n^2)
•N=100000?
•分治法?
•回想刚才分治需要满足的条件
•问题足够小能解决
•子问题互不影响
•子问题可合并
•满足!可以分治
•对于一个点集S,若点集大小为1,则无最近点对,最近距离d=INF
•若点集大小为2,则最近距离为两点距离
•若点集大小大于2,则进行分治。对横坐标进行排序。根据横坐标的大小选取一个点mid,以mid为分界分成两个点集S1与S2进行求解,然后合并答案
•对与第三种情况,记S1的答案为d1,S2的答案为d2,令d=min(d1, d2)。
•若最近点对同在S1或同在S2,则d为答案。
•否则,最近点对中一个点落在S1,一个点落在S2且两点之间的距离<d。
•根据以上分析,我们找到所有与mid点横坐标之差<d的点,进行后续操作。
•这时候是不是直接枚举mid两边的点,计算距离就可以了呢?
•特例:S1 S2中的点在一条直线上,这时所有点与mid的横坐标之差均<d
•结论:不能直接枚举
•换一个思路
•有序是我们喜欢的东西
•设这些点的个数为m,将这些点按照纵坐标大小排序
•对于点k,将计算k到k+1..m的距离,实时更新d值
•若纵坐标之差>=d,则退出循环,继续枚举下一个点
三、倍增
洛谷1967 货车运输
#include <bits/stdc++.h>
using namespace std;
struct arr
{
int adj, next, data;
};
struct ar
{
int x, y, v;
};
bool operator < (ar a, ar b)
{
return a.v > b.v;
}
const int inf = 1e9 + 5;
const int maxn = 100005;
ar b[maxn];
arr g[2*maxn];
int dep[maxn], F[maxn][20], G[maxn][20], f[maxn];
int n, m, q, x, y, top, a[maxn];
int getf(int t)
{
if (f[t] == t) return t;
return f[t] = getf(f[t]);
}
void dfs(int t)
{
for (int p=a[t]; p; p=g[p].next)
if (dep[g[p].adj] == 0)
{
F[g[p].adj][0] = t;
G[g[p].adj][0] = g[p].data;
dep[g[p].adj] = dep[t] + 1;
dfs(g[p].adj);
}
}
void prework()
{
for (int j=1; j<=19; ++j)
for (int i=1; i<=n; ++i)
if (F[i][j-1] != 0)
F[i][j] = F[F[i][j-1]][j-1],
G[i][j] = min(G[i][j-1], G[F[i][j-1]][j-1]);
}
int query(int x, int y)
{
if (getf(x) != getf(y))
return -1;
int ans = inf;
if (dep[x] < dep[y]) swap(x, y);
for (int i=19; i>=0; --i)
if (dep[x] - dep[y] >= (1 << i))
ans = min(ans, G[x][i]),
x = F[x][i];
if (x == y) return ans;
for (int i=19; i>=0; --i)
if (F[x][i] != F[y][i]) //跳到不同点
ans = min(ans, min(G[x][i], G[y][i])),
x = F[x][i],
y = F[y][i];
ans = min(ans, min(G[x][0], G[y][0])); //再跳一步
return ans;
}
void kruskal()
{
for (int i=1; i<=n; ++i) f[i] = i;
sort(b + 1, b + m + 1);
for (int i=1; i<=m; ++i)
{
int x = getf(b[i].x);
int y = getf(b[i].y);
if (x == y) continue;
g[++top].adj = b[i].x; g[top].data = b[i].v;
g[top].next = a[b[i].y]; a[b[i].y] = top;
g[++top].adj = b[i].y; g[top].data = b[i].v;
g[top].next = a[b[i].x]; a[b[i].x] = top;
f[x] = y;
}
}
int main()
{
scanf("%d%d", &n, &m);
for (int i=1; i<=m; ++i)
scanf("%d%d%d", &b[i].x, &b[i].y, &b[i].v);
kruskal();
for (int i=1; i<=n; ++i)
if (dep[i] == 0)
{
dep[i] = 1;
dfs(i);
}
prework();
scanf("%d", &q);
while (q--)
{
scanf("%d%d", &x, &y);
printf("%d\n", query(x, y));
}
return 0;
}