搜索

Intro

搜索差不多也可以说算是一种暴力枚举策略,从初始状态开始,逐渐扩大寻找范围,直到找到答案。但是和直接枚举相比减少了一些无效状态,提高了效率,在数据规模不大时搜索仍然是一种有效的办法。根据搜索的特性可以分为深度优先搜索和广度优先搜索。

深度优先搜索

在枚举某种填空方式时,我们先枚举这个空所有可能的选项,如果有合法的就填下一个选项,然后继续,如果这个空所有选项都不合法,那么就回到上一个空尝试更换选项,继续枚举。这种方式称为回溯算法,常用深度优先搜索来实现。

代码框架

void dfs(int k){//k代表递归层数,或者说要填第几个空
    if(所有空都填完了){
        判断最优解或记录答案;
        return ;
    }
    for(枚举这个空能填的选项){
        if(这个选项是合法的){
            记录下这个空(保存现场);
            dfs(k+1);
            取消这个空(恢复现场);
        }
    }
}

例题

四阶数独 (枚举排列)

const int cnt = 5;
const int N = 16;

int a[cnt*cnt],ans;

bool row[cnt][cnt],col[cnt][cnt],block[cnt][cnt];

void dfs(int k){
    if(k > N){
        for(int i = 1 ; i <= N ; i ++ ){
        printf("%d",a[i]);
        if(i%4 == 0)puts("");
        }
        puts("");
        return;
    }
    int r = (k-1)/4+1,c = (k-1)%4+1,b = (r - 1)/2*2+(c - 1)/2 + 1;
    
    for(int i = 1 ; i <= 4;i++){
        if(!row[r][i] && !col[c][i] && !block[b][i]){
            row[r][i] = col[c][i] = block[b][i] = true;
            a[k] = i;
            dfs(k+1);
            row[r][i] = col[c][i] = block[b][i] = false;
        }
        
    }
}

P1219 [USACO1.5]八皇后 Checker Challenge (枚举排列)

int path[N],ans,n;
bool col[N],dg[N],udg[N];

void dfs(int k){
    if(k>n){
        ans ++;
        if(ans<=3) {
            for(int i = 1 ;  i <= n; i ++ ){
                printf("%d ",path[i]);
            }
            cout<<endl;
        }
        return ;
    }
    for(int i = 1; i <= k ; i ++){
        int ndg = i - k + n - 1;
        int nudg = i + k - 1;
        if(!col[i] && !dg[ndg] && !udg[nudg]){
            path[k] = i;
            col[i] = dg[ndg] = udg[nudg] = true;
            dfs(k+1);
            col[i] = dg[ndg] = udg[nudg] = false;
        }
    }
}

P2392 kkksc03考前临时抱佛脚 (枚举子集)

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 20;

int a[N];

int s[4],maxtime,maxdepth,nowtime,sum;

void dfs(int x){
    if(x > maxdepth){
        maxtime = max(nowtime,maxtime);
        return ;
    }
    if(nowtime+a[x]<=sum/2){
        nowtime += a[x];
        dfs(x+1);
        nowtime -= a[x];
    }
    dfs(x+1);
}

int main(){
    cin>>s[0]>>s[1]>>s[2]>>s[3];
    int ans = 0;
    for(int i = 0 ; i < 4; i ++){
        sum = 0;
        maxdepth = s[i];
        nowtime = 0;
        maxtime  = 0;
        for(int j = 1 ; j <= s[i] ; j ++){
            cin>>a[j];
            sum += a[j];
        }
        dfs(1);
        ans += (sum - maxtime);
    }
    
    cout<<ans<<endl;
    return 0;
}

广度优先搜索

广度优先搜索的搜索树是一层一层的,他会优先到达离初始状态最近的状态,常见的应用是求边权为1的最短路。

代码框架

q.push(初始状态);//将初始状态入队
while(!q.empty()){
    State u = q.front();//取出队首
    q.pop();//出队
    for(枚举所有可扩展状态)//找到u的所有可达状态v
    {
        if(是合法的){//v需要满足某些条件,如未访问过,未在队内等
            q.push(v);//入队(同时可能需要维护某些必要信息)
        }
    }
}

例题

P1443 马的遍历

#include <iostream>
#include <queue>
#include <cstring>

using namespace std;

const int N = 450;
typedef pair<int,int> pii;

int d[N][N],n,m,x0,y0;

int dx[8] = {2,2,-2,-2,1,1,-1,-1},dy[8] = {1,-1,1,-1,2,-2,2,-2};

int main(){
    memset(d,-1,sizeof d);
    cin>>n>>m>>x0>>y0;
    d[x0][y0] = 0;
    queue<pii> q;
    q.push({x0,y0});
    while(q.size()){
        auto t = q.front();
        q.pop();
        for(int i = 0 ; i < 8 ; i ++){
            int x = t.first + dx[i],y = t.second + dy[i];
            if(d[x][y] == -1 && x >= 1 && x <= n && y>=1 && y<= m){
                q.push({x,y});
                d[x][y] = d[t.first][t.second] + 1;
            }
        }
    }
    for(int i = 1; i <= n ; i ++){
     for(int j = 1 ; j <= m ; j ++){
         printf("%-5d",d[i][j]);
     }
     puts("");
    }
    return 0;
}

P1135 奇怪的电梯

#include <iostream>
#include <cstring>
#include <queue>

using namespace std;

const int N = 250;


int d[N], dh[2] = {-1,1};

int k[N],n,a,b;

int main(){
    memset(d,-1,sizeof d);
    cin>>n>>a>>b;
    for(int i = 1 ; i <= n ; i ++)cin>>k[i];
    queue<int> q;
    q.push(a);
    d[a] = 0;
    while(q.size()){
        int t = q.front();
        q.pop();
        for(int i = 0 ; i < 2 ; i ++){
            int h = t + dh[i]*k[t];
            if(d[h]==-1 && h >= 1 && h<=n){
                d[h] = d[t] + 1;
                q.push(h);
            }
        }
    }
    cout<<d[b]<<endl;
    return 0;
}

P2895 [USACO08FEB]Meteor Shower S

#include <iostream>
#include <cstring>
#include <queue>
using namespace std;

const int N  = 350;
int m,death[N][N],map[N][N],ans = 100000;
int dx[4] = {1,-1,0,0},dy[4] = {0,0,1,-1};
struct coord{
    int x;
    int y;
};
int main(){
    memset(map,-1,sizeof map);
    memset(death,0x7f,sizeof death);
    cin>>m;
    for(int i = 0 ; i < m ; i ++){
        int a,b,c;
        cin>>a>>b>>c;
        death[a][b] = min(death[a][b],c);
        for(int i = 0 ; i < 4;i++){
            int u = a+dx[i],v=b + dy[i];
            if(u>=0 && v>=0)
            death[u][v] = min(death[u][v],c);
        }
    }
    queue<coord> q;
    map[0][0] = 0;
    q.push((coord){0,0});
    while(q.size()){
        auto f = q.front();
        q.pop();
        int ux = f.x,uy = f.y;
        for(int i = 0 ; i < 4;i++){
            int fx = ux + dx[i],fy = uy+dy[i];
            if(fx>=0 && fy>=0 && map[fx][fy] == -1&&map[ux][uy]+1<death[fx][fy]){
                map[fx][fy] = map[ux][uy] + 1;
                q.push((coord){fx,fy});
            }
        }
    }
    for(int i = 0 ; i < 305 ; i ++){
        for(int j = 0 ; j < 305;j++){
            if(death[i][j]>1000 && map[i][j]!=-1)
            ans = min(ans,map[i][j]);
        }
    }
    if(ans == 100000) cout<<-1<<endl;
    else cout<<ans<<endl;
    
    return 0;
    
    
}

Summary

搜索都是寻找目标解,dfs 常常寻找的是字典序最小的,而 bfs 寻找步骤最少的,要根据需要来选择算法。

posted @ 2021-04-27 17:38  今天AC了吗  阅读(66)  评论(0)    收藏  举报