【总结】搜索

搜索

1.深度优先搜索

英文名称Depth First Search,简称DFS

简单概括:在搜索的过程中一条路走到黑

对于树:

递归实现。复杂度:O(n)

每条边会走两次,一次从儿子到父亲,一次从父亲到儿子

void dfs(int x){
	int i;
	for(i=1;i<=n;i++){
		if(mp[x][i]&&i!=fa[x]){
			fa[i]=x;
			dfs(i)
		}
	}
}

对于图:

同树一样,加一个bool数组保证只访问一次

但大部分时候我们并不是对图进行dfs,用dfs搜索的复杂度常常是指数级别。

void dfs(int x){
	int i;
	vis[x]=1;
	for(i=1;i<=n;i++){
		if(mp[x][i]&&!vis[i]){
			dfs(i)
		}
	}
}

用处

1.深度优先搜索可以枚举一个集合的所有子集

void dfs(int x){
	if(x>n){
		//得到了一个子集 
		return ;
	}
	s[++tot]=a[x];
	dfs(x+1);
	tot--;
	dfs(x+1);
}

2.求全排列

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;
		}
	}
}

3.dfs求最短路,用一个dis数组记录到达这个节点最短路的距离,当走到这个节点时如果更优更新这个数组的值

例题

一.八皇后问题

题目链接!:https://www.luogu.com.cn/problem/P1219

整个题我们可以分成n个阶段,在每一行首先放置一个皇后

设第i行的皇后放置在pi列,每次扩展的时候只需考虑是否
存在j < i,使得pi = pj或pi-i = pj j(不在从左下到右上的对角线上)或pi+i = pj + j(不在从右下到左上的对角线上)

如果存在就转移的下一个状态,不存在回溯到上一个状态

代码实现还是很简单的,嗯对。

#include<bits/stdc++.h>//八皇后orz 
using namespace std;
int a[100],b[100],c[100],d[100];

int n,zs=0;
int print(){
	if(zs<=2)
    {
        for(int k=1;k<=n;k++)
        cout<<a[k]<<" ";
        cout<<endl;
    }
    zs++;
}
void queen(int i){
	if(i>n){
	print();
	return;
	}
	else{
		for(int j=1;j<=n;j++){
			if((!b[j])&&(!c[i+j])&&(!d[i-j+n])){
				a[i]=j;
				b[j]=1;
				c[i+j]=1;
				d[i-j+n]=1;
				queen(i+1);
				b[j]=0;
				c[i+j]=0;
				d[i-j+n]=0;
			}
		}
	}
}
int main(){
	cin>>n;
	queen(1);
	cout<<zs;
	return 0; 
}

二.生日蛋糕

题目链接:https://www.luogu.com.cn/problem/P1731

我们可以进行dfs,对于每一层蛋糕暴力枚举它的半径和高度,记录好当前的状态:第i层的半径,第i层的高度,当前已经用掉的蛋糕体积,当前表面积,当前用到的蛋糕层数。然后再去搜索下一层。

啊显然这样,他就,炸掉!!!!(不是

我们考虑剪枝:

最优化剪枝:如果当前已用表面积加上余下最小的侧面积大于已知最优解,剪枝

可行性剪枝:剩下的材料太多或太少,做不了恰好剩下的层数,剪枝

2.广度优先搜索

英文名称Breadth First Search,简称BFS,又称宽度优先搜索

同样是很基础的搜索算法

用队列实现

在搜索的任意时刻,已经被访问过的点依旧是一个连通块

按照深度从小到大依次遍历

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;
			}
		}
	}
}

用处

1.给定边权为1的图bfs求单源最短路

2.对于边权为1或2的图bfs求单源最短路(将边权为2的点拆分为两个边权为1的点

3.给定边权为1或0的图bfs求单源最短路

分析

我们对于每一个点,可以先只考虑它出边为0连到的点进行dfs,将这几个点缩成一个点来考虑,用cnt记录这个点的标号,再去搜这个点出边为1的点依次进行上述操作,每搜索到一个点就cnt++记录这个点的编号。

或者

大致也算是缩点的思路,我们可以考虑如果一个点到下一个点连边的边权为0,相当于两个点是在同一深度,我们对一个点进行bfs如果出边为0,那么这条边所连接到的那个点就是直接放在队列的对头,如果是1则放到队尾。同时我们的vis数组(记录是否到达过)应该换成dis记录i的距离

例题

一.八数码问题

题目链接!:https://www.luogu.com.cn/problem/P1379

由于棋盘是3*3的,也就是9位,将空白的格子当做9我们可以将每一种棋盘的状态转换为一个由数字1-9组成的9位数字

这样,我们已知初始9位数字(也就是初始棋盘状态对应的九位数),和最终要变换为的9位数

我们每次可以将当前状态的九位数中按的两位数按规定调换位置转换到下一个状态

于是便可以通过bfs搜索从初始到最终的最短路。

啊这个显然很好想,

但是我们每一次进行转换的是一个9位数字

对于这些9位数字我们需要用一个bool数组来存储判断它是否已经遍历过

那这样数组需要开到九位,很明显数组会炸

虽然我们开了九位数的但是八位数的那一部分我们就浪费了鸭

我们考虑,可以对于每一个9位数构造一个映射让每个九位数都对应一个不同的更小的数

我们规定123456789为第一个数,之后按照字典序依次往后对应一个数

这样就只需要存下9!个数字是完全可以存下来的辣。

二.寻找道路

题解写过了qwq:https://www.cnblogs.com/huixinxinw/p/12236699.html

#include<iostream>
#include<cstdio>
#include<vector>
#include<queue>
#include<string>
#include<cstring>
using namespace std;
int read(){
    int a = 0,f = 0;char p = getchar();
    while(!isdigit(p)){f|=p=='-';p = getchar();}
    while(isdigit(p)){a=(a<<3) + (a<<1) +(p^48);p = getchar();}
    return f?-a:a;
}
vector<int> mmp[10001];
vector<int> f[10001];
int n,m,s,t;
queue<int> q;
bool flag[20000];//
bool del[20000];//
bool vis[20000];
priority_queue<pair<int,int> > qwq;
int dis[20000];
void bfs(int x){
    q.push(x);
    flag[x] = 1;
    while(!q.empty()){
        int u = q.front();
        //cout<<u<<endl;
        for(int i = 0;i < f[u].size();i++){
            if(!flag[f[u][i]]){
                flag[f[u][i]] = 1;
                q.push(f[u][i]);
            }
        }
        q.pop();
    }
}
void delet(){
    for(int i = 1;i <= n;i ++){
        for(int j = 0;j < mmp[i].size();j++){
            int v = mmp[i][j];
            if(flag[v] == 0){
                //cout<<v<<" "<<i<<endl;
                del[i] = 1;}
        }
    }
}
void _short(){
    int x,y;
    memset(dis,1061109567,sizeof(dis));
    dis[s] = 0;
    qwq.push(make_pair(0,s));//答案 编号
    while(!qwq.empty()){
        x = qwq.top().second;
        qwq.pop();
        if(vis[x])continue;
        vis[x] = 1;
        for(int i = 0;i < mmp[x].size();i++){
            y = mmp[x][i];
            if(del[y])continue;
            if(dis[y] > dis[x] + 1){
                dis[y] = dis[x] + 1;
                qwq.push(make_pair(-dis[x]-1,y));
            }
        }
    }
    //for(int i = 1; i<= n; i++)cout<<dis[i]<<endl;
    if(dis[t] >= 1061109567)cout<<-1<<endl;
    else cout<<dis[t]<<endl;
}
int main(){
    //cout<<0x3f<<endl;
    n = read();m = read();
    int x,y;
    for(int i = 1;i <= m;i++){
        x = read();y = read();
        if(x == y)continue;
        mmp[x].push_back(y);
        f[y].push_back(x);
    }
    s = read();t = read();
    bfs(t);
    if(flag[s] == 0){cout<<"-1";return 0;}
    delet();
    if(del[s] == 1){cout<<"-1";return 0;}
    _short();
}

三.Tales of seafaring (回去整理

题目链接:https://www.luogu.com.cn/problem/P2296

大致思路比较好想:这个题好做是由于题目中给出条件,路径不一定是简单路

只要我走的路径小于d,奇偶性与d相同,就一定能绕一段路使路径长度达到d。

接下来就是关键的实现过程:(趴

我们将一个点拆为两个点,一个点记录奇数情况下最短路,另一个记录偶数情况下最短路,记录奇数的点向下一个偶数点连边,偶数点向下一个奇数点连边。

4.IDFS

是什么

(从课件上复制的)

•我们已经学会了dfs和bfs

•然而有的问题还是使我们无法进行搜索,因为你要进行搜索的图可能是无限大的,每个点所连的边也可能是无限多的,这就使得dfs和bfs都失效了,这时候我们就需要用到idfs

•我们枚举深搜的时候深度的上限,因为深度上限的限制,图中的一些边会被删掉,而图就变成了一个有限的图,我们就可以进行dfs了

然后 大概是下面这个样子的qwq

void dfs(){
	if(x>lim){
		return ;
	}
	//搜索代码 
}
int main(){
	for(lim=1;;lim++){
		dfs(1);
	}
}

例题

埃及分数

(等有空就把坑填了

题目链接:https://www.luogu.com.cn/problem/UVA12558

5.A*

例题

k短路问题

以当前点到终点的最短路作为h(x)

(代码是老师写的

#include<queue>
#include<cstdio>
#include<algorithm>
#define N 5005
#define M 200010
using namespace std;
double dis[N],ee;
int n,m;
bool vis[N];
struct node
{
	int to;double g;
	double f(){return g+dis[to];}
	friend bool operator<(node x,node y)
	{return x.f()==y.f()?x.g>y.g:x.f()>y.f();}
};
struct E
{
	struct edge{int next,to;double v;}e[M];
	int head[N],tot;
	void add(int u,int v,double w)
	{
		e[++tot]=(edge){head[u],v,w};
		head[u]=tot;
	}
	void SPFA(int s)
	{
		queue<int>q;
		for(int i=1;i<=n;i++)dis[i]=1e9;
		dis[s]=0;q.push(s);
		while(!q.empty())
		{
			int now=q.front();q.pop();
			vis[now]=0;
			for(int i=head[now];i;i=e[i].next)
			if(dis[now]+e[i].v<dis[e[i].to])
			{
				dis[e[i].to]=dis[now]+e[i].v;
				if(!vis[e[i].to])
				vis[e[i].to]=1,q.push(e[i].to);
			}
		}
	}
	int Astar()
	{
		priority_queue<node>q;
		int cnt=0;double t=0;
		node x,now;
		x.to=1;x.g=0;
		q.push(x);
		while(!q.empty())
		{
			x=q.top();q.pop();
			if(x.to==n)
			{
				t+=x.g;
				if(t>ee)return cnt;
				cnt++;
			}
			for(int i=head[x.to];i;i=e[i].next)
			{
				now.to=e[i].to;
				now.g=x.g+e[i].v;
				q.push(now);
			}
		}
	}
}E1,E2;
int main()
{
	scanf("%d%d%lf",&n,&m,&ee);
	int x,y;double z;
	while(m--)scanf("%d%d%lf",&x,&y,&z),
	E1.add(x,y,z),E2.add(y,x,z);
	E2.SPFA(n);
	printf("%d",E1.Astar());
}

6.双向搜索

每次扩展不仅扩展起始节点端,同时扩展终点端的搜索状态,直到中途相遇

例题

一. 数字变换

•给出一个数S,另外给出一个数T,每次可以把一个数乘
以3或者整除2

•求最少需要多少步变换(保证答案小于40)

分析

每次进行两种操作如果我们暴力去搜索的话 一共会有\(40^2\)种可能出现的情况,它会tle

我们进行双向搜索,对于从初始状态开始搜,每一次变换可能乘以3或者除以2。同时我们也从最终状态从后往前搜,每一次变换可能是除以3(必须是三的倍数)或者乘以2或乘以2加一。直到两个集合中出现相同的两个数字,两种变换次数相加即为最终所求。

二.Lizard Era: Beginning

(等我有空就把坑填上

posted @ 2020-01-27 19:56  蕙心心w  阅读(650)  评论(0编辑  收藏  举报