#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

posted @ 2020-01-16 11:38  CYC的幸福生活  阅读(321)  评论(0编辑  收藏  举报