搜索
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
(等我有空就把坑填上
