搜索算法

洛谷1605题

  要注意的点:

  在地图中,有障碍的地方和已经走过的地方性质是一样的,不必为了障碍专门开一个数组,浪费空间。

  记得初始化:初始点相当于已经走过了,不要再走。(巨坑,时隔半年才被我发现)

code:

#include <bits/stdc++.h>
using namespace std;
int total,n,m,sx,sy,fx,fy,t;
bool a[7][7];
int dx[]={1,-1,0,0};
int dy[]={0,0,1,-1};
void f(int x,int y){
    if(x==fx && y==fy){
        total++;
        return;
    }
    for(int i=0;i<4;i++){
        int tx=x+dx[i],ty=y+dy[i];
        if(tx>=1 && ty>=1 && tx<=m && ty<=n && a[tx][ty]==false){
            a[tx][ty]=true;
            f(tx,ty);
            a[tx][ty]=false;
        }
    }
}
int main(){
    cin>>n>>m>>t>>sx>>sy>>fx>>fy;
    a[sx][sy]=true;
    for(int i=1;i<=t;i++){
        int x,y;
        cin>>x>>y;
        a[x][y]=true;
    }
    f(sx,sy);
    cout<<total;
    return 0;
}

洛谷1644题:

  非常简单的DFS,连回溯都用不上:因为小马只会往右跳,根本跳不回左边去。所以连图都不用建,毕竟建图是为了储存已经走过的信息,这里不可能有重复走的可能(有递进条件只会往右跳决定),所以不建图嘿嘿。

code:

#include <bits/stdc++.h>
using namespace std;
int total,n,m;
bool a[20][20];
int dx[]={1,1,2,2};
int dy[]={2,-2,1,-1};
void f(int x,int y){
    if(x==m && y==n){
        total++;
        return;
    }
    for(int i=0;i<4;i++){
        int tx=x+dx[i],ty=y+dy[i];
        if(tx>=0 && ty>=0 && tx<=m && ty<=n && a[tx][ty]==false){
            a[tx][ty]=true;
            f(tx,ty);
            a[tx][ty]=false;
        }
    }
}
int main(){
    cin>>n>>m;
    a[0][0]=true;
    f(0,0);
    cout<<total;
    return 0;
}

洛谷1219:

  经典的八皇后问题啊,关键之处在于用下标之和,下标之差表示双对角线,这里的对角线和跳马,迷宫之中的地图是一样的,只不过这里的”地图“不止一个,而是有行,左右对角线三处。此题中还有一个关键点在于表示出前三个回答,我们在走迷宫类问题中也会遇到这种需求:写出走迷宫的路径,怎么解决呢?当然是在搜索过程中把每一步储存在一个路径数组stepx,stepy中,一旦可以到达终点那就输出这两个step数组中记录的路径,不用专门去清空这两个数组,因为每次搜索新的路径都会覆盖原有的路径,无论新路径是对的还是错的,最终能不能走到终点,都会覆盖,但只有成功的路径才会被输出。

 code:

#include <bits/stdc++.h>
using namespace std;
int total,n,a[15],b[15],c[30],d[30];
void f(int step){
    if(step==n+1){
        total++;
        if(total<4){
            for(int i=1;i<=n;i++){
                cout<<a[i]<<' ';
            }
            cout<<endl;
        }
        return;
    }
    for(int j=1;j<=n;j++){
        if(b[j]==0 && c[step+j]==0 && d[step-j+n]==0){
            b[j]=1,c[step+j]=1,d[step-j+n]=1,a[step]=j;
            f(step+1);
            b[j]=0,c[step+j]=0,d[step-j+n]=0;
        }
    }
}
int main(){
    cin>>n;
    f(1);
    cout<<total;
    return 0;
}

洛谷1596:

看看哪个点是有水的,顺着这个点延伸出去,把所有和它是一个坑的都变成干旱,最后数一数整个图中有几个点是有水的,那就有几个水坑。我愿称之为缩点法——缩片成点。

#include <bits/stdc++.h>
using namespace std;
int total,n,m;
char a[105][105];
int dx[]={0,0,-1,-1,-1,1,1,1};
int dy[]={1,-1,-1,0,1,-1,0,1};
void f(int x,int y){
    for(int i=0;i<8;i++){
        int tx=x+dx[i],ty=y+dy[i];
        if(tx>=1&&ty>=1&&tx<=n&&ty<=m&&a[tx][ty]=='W'){//注意,tx对应的是n,而不是x坐标
            a[tx][ty]='.';
            f(tx,ty);
        }
    }
}
int main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            cin>>a[i][j];
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            if(a[i][j]=='W'){
                a[i][j]='.';
                f(i,j);
                total++;
            }
        }
    }
    cout<<total;
    return 0;
}

注意横纵坐标,比如跳马那一题,数据先给列数再给行数,就是为了误导你,让你把行当列用,把列当行用,让你过不了。

 

注意,BFS如果要回溯的话时间复杂度是指数级,所以数据规模通常不大。BFS通常用于解决方案数(1219八皇后,1644跳马,马拉车),数个数(1596水坑数)类问题。

DFS:主要解决最优解类问题。

洛谷1379八数码难题

  这题的思路是简单的BFS,没什么好说,问题在于如何表示状态,但如果用一个字符串(哈希)来储存该结构,我就可以用map和int作为键和值来储存每种状态的步数(从初始状态走到现在走了多少步)。这里注意map的用法。

  注意用字符串表示图案时,用字符串的find函数找出0的位置,再用/和%找到横纵坐标。

  在写代码过程中有个错误:BFS是用不到函数递归的,整个过程中只调用了一次函数,而DFS则用到递归,要反复调用函数。

  自己的一个小优化:如果找到的话就直接return,不用把每种方案都搜索出来,节省了许多时间。

code:

#include <bits/stdc++.h>
using namespace std;
int total;
string res="123804765";
int dx[]={0,0,-1,1};
int dy[]={1,-1,0,0};
map<string,int> d;
queue<string>q;
void f(int step){
    while(q.size()){
        string t=q.front();
        if(t==res){
            cout<<step;
            return;
        }
        step=d[t];
        q.pop();
        int k=t.find('0');
        int x=k/3,y=k%3;
        for(int i=0;i<4;i++){
            int tx=x+dx[i],ty=y+dy[i];
            if(tx>=0&&ty>=0&&tx<3&&ty<3){
                swap(t[k],t[tx*3+ty]);
                if(d[t]==0)d[t]=step+1,q.push(t);
                swap(t[k],t[tx*3+ty]);
            }
        }
    }
}
int main(){
    string begin;
    cin>>begin;
    q.push(begin);
    f(0);
    return 0;
}

一道很有趣的条件纠缠问题:

某侦察队接到一项紧急任务,要求在A、B、C、D、E、F六个队员中尽可能多地挑若干人,但有以下限制条件:

1)A和B两人中至少去一人;

2)A和D不能一起去;

3)A、E和F三人中要派两人去;

4)B和C都去或都不去;

5)C和D两人中去一个;

6)若D不去,则E也不去。

试编写一个程序,输出问应当让哪几个人去?

解法:用深搜遍历每一种可能性。将约束条件写成函数即可求出所有合理的解法,在解法中找到去的人数最多的那一种即可。

code:(本题是C语言程序设计实践的上机题,所以使用C语言写成)(注意约束条件是怎么写的,不要把”ABC有两个人去“写成“ABC都去不行”||“ABC都不去不行”||“A去了BC没去”……这种形式,这一看头都快破掉了,然而我最初就真是这么写的……我这个脑子呦)

#include <bits/stdc++.h>
using namespace std;
bool a[7];
int res,total;
bool right(){
    int t=0;
    if(a[0]==true)t++;
    if(a[1]==true)t++;
    if(t==0)return false;
    
    
    t=0;
    if(a[0]==true)t++;
    if(a[3]==true)t++;
    if(t==2)return false;
    
    
    t=0;
    if(a[0]==true)t++;
    if(a[4]==true)t++;
    if(a[5]==true)t++;
    if(t!=2)return false;
    
    
    t=0;
    if(a[1]==true)t++;
    if(a[2]==true)t++;
    if(t==1)return false;
    
    
    t=0;
    if(a[3]==true)t++;
    if(a[2]==true)t++;
    if(t!=1)return false;
    
    
    if(a[3]==false && a[4]==true)return false;
    
    
    return true;
}
void f(int step){
    if(step==7){
        if(res>total && right()){
            total=res;
            for(int i=0;i<7;i++){
                cout<<a[i]<<' ';
            }
            cout<<endl;
        }
        return;
    }
    else{
        a[step]=true;
        res++;
        f(step+1);
        res--;

        a[step]=false;
        f(step+1);
    }
}
int main(){
    for(int i=0;i<7;i++){
        cout<<char('A'+i)<<' ';
    }
    cout<<endl;
    f(0);
    cout<<total;
    return 0;
}

再注意一点:DFS是有模板的,虽说不要求背诵模板,但起码要有个大致的印象,比如本题中前6层的递归都是在找答案,到了第七层就输出答案,所以出口的条件就是if(n==6)

对了,if的出口之后记得加一个else,这是必备的,否则就会数组越界。

记住,汉诺塔问题需要用高进度

与其自己手写高精度加法,不如直接使用python3:

 

 

n = eval(input())
inf = 2147483647
f1=[inf for i in range(0,n+1)]
f2=[inf for i in range(0,n+1)]
f=[f1,f2]
f[0][0]=f[1][0]=0
f[0][1]=f[1][1]=1
for i in range(2,n+1):
    f[0][i]=2*f[0][i-1]+1
for i in range(2,n+1):
    for j in range(1,i+1):
        f[1][i]=min(f[1][i],f[1][j]*2+f[0][i-j])
print(f[1][n])

搜索的时候要注意剪枝

不需要的数组不要开,这题一开始我还加了标记是否为空的empty数组,是否为真的real数组,并且用vector来储存v,这些都是可以省的。平时没什么,但这题的时间卡的很死,就需要高度剪枝了。

 

#include<iostream>
using namespace std;
int n,px,py;
int clue[505],v[3],p;
bool check(){//px,py空,满足条件吗
    p=0;
    for(int i=1;i<=n;i++){
        if(p>2)return 0;
        if(clue[i]>0){
            if(clue[i]==px||clue[i]==py){
                v[p++]=i;
            }
        }else{
            if(-clue[i]!=px&&-clue[i]!=py){
                v[p++]=i;
            }
        }   
    }
    if(p==2){
        int fal_ful=0,fal_emp=0;
        for(int i=0;i<2;i++){
            if(v[i]==px||v[i]==py)fal_emp++;
            else fal_ful++;
        }
        if(fal_emp==1&&fal_ful==1)return 1;
    }
    return 0;
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++)scanf("%d",&clue[i]);
    for(px=1;px<=n;px++){
        for(py=px+1;py<=n;py++){
            if(check()){
                printf("%d %d",px,py);
                return 0;
            }
        }
    }
    printf("No Solution");
    return 0;
}

 数独这种经典题目能不能在三秒之内写出来:

 

#include <bits/stdc++.h>
using namespace std;
bool col[9][10],row[9][10],nine[3][3][10];
char a[10][10];
bool dfs(int x,int y){
    if(x==9){
        for(int i=0;i<9;i++)puts(a[i]);
        return 1;
    }
    if(y==9)return dfs(x+1,0);
    if(a[x][y]!='.')return dfs(x,y+1);
    for(int i=1;i<=9;i++){
        if(!col[y][i]&&!row[x][i]&&!nine[x/3][y/3][i]){
            col[y][i]=row[x][i]=nine[x/3][y/3][i]=1;
            a[x][y]='0'+i;
            if(dfs(x,y+1))return 1;
            col[y][i]=row[x][i]=nine[x/3][y/3][i]=0;
            a[x][y]='.';
        }
    }
    return 0;
}
int main(){
    cin.tie(0);
    cin.sync_with_stdio(false);
    for(int i=0;i<9;i++){
        scanf("%s",a[i]);
        for(int j=0;j<9;j++){
            if(a[i][j]!='.'){
                col[j][a[i][j]-'0']=row[i][a[i][j]-'0']=nine[i/3][j/3][a[i][j]-'0']=1;
            }
        }
    }
    dfs(0,0);
    return 0;
}

 经典深搜剪枝问题:

小木棍+小猫爬山,两题极为相似,唯一不同之处在于木棍的长度等于分开木棍长度之和;缆车重量只要大于小猫重量之和即可。

这里的剪枝方式非常经典,务必记住。

小猫爬山:

最初试图用return 0表示不可行,return 1表示可行,但是在在新找了一辆缆车的分支中没有将状态回退,所以debug好几个小时。

记录一个神奇的宽搜题目:立体推箱子,本质是宽搜,但形式极其操蛋

#include<bits/stdc++.h>
using namespace std;
const int N=510,INF=0x7f7f7f7f;
int n,m,ans,visit[N][N][3];
char c,a[N][N];
//立0,横1,标记左,竖2,标记上;
//上右下左
//竖着x横着y,竖着n横着m
int dx[3][4]={
    {-2,0,1,0},
    {-1,0,1,0},
    {-1,0,2,0}
};
int dy[3][4]={
    {0,1,0,-2},
    {0,2,0,-1},
    {0,1,0,-1}
};
int ds[3][4]={
    {2,1,2,1},
    {1,0,1,0},
    {0,2,0,2}
};
struct node{int x,y,state;}final,start;
bool check(node x){
    if(x.x<1||x.y<1||x.x>n||x.y>m)return 0;
    if(x.state==0&&(a[x.x][x.y]=='#'||a[x.x][x.y]=='E'))return 0;
    if(x.state==1&&(
        x.y+1>m||
        a[x.x][x.y]=='#'||
        a[x.x][x.y+1]=='#'
    ))return 0;
    if(x.state==2&&(
        x.x+1>n||
        a[x.x][x.y]=='#'||
        a[x.x+1][x.y]=='#'
    ))return 0;
    return 1;
}
void bfs(){
    queue<pair<node,int>> q;
    q.push({start,0});
    while(q.size()){
        auto u=q.front();
        int ux=u.first.x,uy=u.first.y,us=u.first.state;
        q.pop();
        for(int i=0;i<4;i++){
            node v={ux+dx[us][i],uy+dy[us][i],ds[us][i]};
            if(visit[v.x][v.y][v.state]==0&&check(v)){
                if(
                    v.x==final.x&&
                    v.y==final.y&&
                    v.state==final.state
                ){ans=u.second+1;return;}
                visit[v.x][v.y][v.state]=1;
                q.push({v,u.second+1});
            }
        }
    }
}
int main(){
    while(scanf("%d%d",&n,&m)){
        if(!n)return 0;
        int first=-1;
        ans=INF;
        memset(visit,0,sizeof visit);
        getchar();
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                a[i][j]=getchar();
                if(a[i][j]=='X'){
                    if(first==-1){
                        start={i,j,0};
                        first=0;
                    }else{
                        if(start.x==i-1){
                            start.state=2;
                        }else{
                            start.state=1;
                        }
                    }
                }
                if(a[i][j]=='O'){
                    final={i,j,0};
                }
            }
            getchar();
        }
        bfs();
        if(ans==INF)printf("Impossible\n");
        else printf("%d\n",ans);
    }
    return 0;
}

一道需要剪枝的深搜题目:

剪枝方法是:记忆化搜索。

最初我的记忆化内容是从某个点走到终点的最短距离,计算出某个点的该值以后,再次进入该点时就直接返回该值;

实际上每次两次到达某点时visit中已经走过的点不同,所以最短距离不能一概而论;

真正的记忆化搜索是记录走到当前点的

posted @ 2022-08-05 22:59  _a_rk  阅读(69)  评论(0编辑  收藏  举报
浏览器标题切换
浏览器标题切换end