#QBXT2020寒假 Day 1
枚举法
水仙花数
寻找三位数枚举9的全排列,检查是否成比例
模拟
海港
暴力模拟:\(O(nk)\)
正解:\(O(\sum k_i)\)
count[i]表示i国有多少人
深度优先搜索
遍历。
树的DFS
void dfs(int x){
int i;
for(i=1;i<=n;i++){
if(mp[x][i]&&i!=fa[x]){
fa[i]=x;
dfs(i)
}
}
}//lcez_cyc
图的DFS
加一个vis数组,标记是否已经访问过
void dfs(int x){
int i;
vis[x]=1;
for(i=1;i<=n;i++){
if(mp[x][i]&&!vis[i]){
dfs(i)
}
}
}
为什么叫深搜?
算法特点:每次DFS的下一个节点都是比当前节点更深的
应用
每次扩展当前状态的所有后继状态,并依次对这些后继状态递归。一般的搜索问题都可以划分为几个阶段,在每个阶段去扩展后继状态
复杂度计算
复杂度分为两个部分,一部分是状态数,一部分是扩展后继状态的复杂度
所以要想优化搜索,要么减小状态数,要么减小扩展后继的计算量
小题
如何用深度优先搜索枚举一个集合的所有子集?
void dfs(int x){
if(x>n){
//得到了一个子集
return ;
}
s[++tot]=a[x];
dfs(x+1);
tot--;
dfs(x+1);//枚举选或是不选
}
小题
如何用深度优先搜索枚举1~n的所有排列?
void dfs(int x){
if(x>n){
//得到了一个排列
return ;
}
int i;
for(i=1;i<=n;i++){
if(!used[i]){
p[x]=i;
used[i]=1;
dfs(x+1);
used[i]=0;
}
}
}
DFS最短路
对于每个点维护一个dis数组,每遍历到一个点就更新
N皇后
按着行的顺序DFS
先把问题分为n个阶段,即从第一行到最后一行,每一行都放置一个皇后,要求与之前放置的皇后不能攻击
设第i行的皇后放置在pi列,每次扩展的时候只需考虑是否存在j < i,使得pi = pj(在同一列)或pi - i = pj - j或pi + i = pj + j(解释:图中的对角线有两种情况:\(y = -x+k\)或者\(y = x+k\),变形得\(y+x = k\),\(y-x = k\),且一个k对应一个斜线)
暴力判断一次复杂度为O(n),如果开一个数组来记录这三个量的出现情况,就可以省掉O(n)的复杂度,这是通过减小扩展后继的计算量来减小复杂度
生日蛋糕
做一个生日蛋糕,生日蛋糕是由m个圆柱体构成的,且满足以下条件
从下往上,圆柱半径和高都越来越小,总体积为n
求表面积最小的蛋糕制作方案(底面不算表面积)
剪枝方案:
-
最优化剪枝:如果当前已用表面积加上余下最小的侧面积大于已知最优解,剪枝
其中剩下的体积\(v_i\),最大半径为\(r_i\),则最小表面积就等于\(\frac{2v_i}{r_i}\)
-
可行性剪枝:剩下的材料太多或太少(\(m\pi r_i^2h_i < v_i\) 或者$ v < m \pi$),做不了恰好剩下的层数,剪枝
BFS广度优先搜索
描述
你现在还是在一号点,你还是想找到树中与一号点连通的每一个点
我们初始的时候把一号点推入队取出队尾,然后只要当前队列非空,我们就取出队头元素x,并将队头弹出
然后我们将x的所有儿子推入队列
对于图上的情况,我们将所有与x相连,并且还没入过队的点推入队列
这样我们就能够访问所有点
代码
void bfs(){
q[tl++]=s;
while(hd!=tl){
x=q[hd++];
for(i=1;i<=n;i++){
if(mp[x][i]&&!vis[i]){
q[tl++]=i;
vis[i]=1;
}
}
}
}
为什么叫广度优先搜索呢?
在搜索的任意时刻,已经被访问过的点依旧是一个连通块
并且类比dfs树,我们可以定义bfs树
并且依旧有一个可行点集合
而现在我们每次选择的一定是可行点集合中深度最浅的一个点,也就是说我们优先向更多的子树扩张,向更广阔的地域进发,所以叫做广度优先搜索
最短路扩展
-
给定一个图,边权均为1,如何用广度优先搜索求单源最短路?
BFS遍历,同时记录每个点的最小深度,就可以得到最短路。
-
给定一个图,边权均为1,图中某些点可以作为起点,求每个点与离他最近的起点的距离
把这个起点首先压入队列中 -
给定一个图,边权为1或2,如何用广度优先搜索求单源最短路?
把边权为二的拆成边权为1的边 -
给定一个图,每条边的边权为0或者1,如何用广度优先搜索求单源最短路?
队列中深度是从小到大的
把边权为0的点缩掉:先搜索一遍,把边权为1的删掉。然后把所有连通块缩成一个点实现:count = 1,belong数组,第一遍深搜先把所有与第一个点有0边连接的点i的belong[i] = count(即缩点的代码实现)
或者:BFS时与这个点有0边连接的点世界放到队头就好了,vis数组需要改成dis数组记录i的距离,如果dis更小,就入队(0入队头,1入对尾)
和DFS的区别
BFS类似最短路,BFS自带最短路光环
八数码问题
首先我们已知原始状态,先把原始状态压入队列
接着我们每次有几个扩展,每次取出队头状态扩展后,把新扩展的且未出现过的几个状态压入队列,同时记录每个状态的深度
然后把新的状态压入队列
一共有9!状态,我们可以预先设计一种规则来表示,把一个棋盘状态映射成一个数字,判重则只需要一个数组即可
寻找道路
一个n点m边无向图,边权均为1,有k个询问每次询问给出(s; t; d),要求回答是否存在一条从s到t的路径,长度为d路径不必是简单路(可以自交)n ≤ 5000; m ≤ 5000; k ≤ 100000
考虑枚举起点s,只需要求出从s到t的所有路径中,长度为奇数的路径最短为多少,以及长度为偶数的路径最短为多少
这实际上就是一个状态数为2n的BFS
IDFS
描述:
我们已经学会了dfs和bfs
然而有的问题还是使我们无法进行搜索,因为你要进行搜索的图可能是无限大的,每个点所连的边也可能是无限多的,这就使得dfs和bfs都失效了,这时候我们就需要用到idfs
我们枚举深搜的时候深度的上限,因为深度上限的限制,图中的一些边会被删掉,而图就变成了一个有限的图,我们就可以进行dfs了
代码:
void dfs(){
if(x>lim){
return ;
}
//搜索代码
}
int main(){
for(lim=1;;lim++){
dfs(1);
}
}
埃及🇪🇬分数
我们可以考虑搜索当前最小的分母
但是这样的话图的点数和每个点的度数都是无限的
枚举最多用多少个数
搜索当前最小的分母
这样当分母足够大的时候就不可能是答案//因为前面已经搜索出了答案
图的深度和每个点的度数都变成了有限的
A*
描述
搜索算法经常运行效率很低,为了提高效率,我们可以使用A*算法
我们对每个点定义一个估价函数f(x)=g(x)+h(x)
g(x)表示从起始点到x的实际代价
h(x)表示估计的从x到结束点的代价,并要求h(x)小于等于从x到结束点的实际代价
那么每次我们从可行点集合中找到f(x)最小的x,然后搜索他
这个过程可以用优先队列(即堆)实现
这样的话可以更快地到达结束点,并保证到达结束点时走的是最优路径
举例
比如八数码问题,g(x)在搜索的时候就已经有过,而\(h(x)\)就是这个图上每个点到目标位置的曼哈顿距离之和。设计h时必须保证f小于答案
K短路问题
以当前点到终点的最短路作为h(x)
A*搜索的时候,g为实际,h为最短路
双向搜索
描述
每次扩展不仅扩展起始节点端,同时扩展终点端的搜索状态,直到中途相遇
复杂度
就是原DFS的根号级别\(O(DFS^{\frac12})\)
数字变换
给出一个数S,另外给出一个数T,每次可以把一个数乘以3或者整除2
求最少需要多少步变换(保证答案小于40)
直接宽搜会因为状态数太多而可能超时考虑使用双向搜索,源点的扩展很简单终点对应两种逆运算,如果整除3那么就除以3,以及乘以2或者乘以2再加1
Lizard Era: Beginning
有3个人和n次活动,每个人可以选择是否参加第i次活动,第i次活动需恰好两个人参加第i个人参加第j次活动的收益为ci,j求出一种方案,使得三个人收益相同,n ≤ 25