【搜索】洛谷官方题单刷题总结~递归与枚举~DFS~BFS

文章目录

    • 递归与排列
      • 1.排列型枚举
        • 洛谷-全排列
      • 2.实现指数型枚举
        • 洛谷-临时抱佛脚
        • 洛谷-自然数拆分
      • 3.递归实现组合型枚举
        • 洛谷-选数
    • DFS深度优先搜索
        • 洛谷取数游戏
        • 洛谷八皇后 如何检查?
        • 洛谷-USACO-湖泊数量
        • 洛谷-填涂颜色
        • 洛谷单词方阵
    • BFS广度优先搜索
        • 洛谷-马的遍历
        • 洛谷Meteor_Shower_S

递归与排列

1.排列型枚举

洛谷-全排列

按照字典序输出自然数 1 到 n 所有不重复的排列,即 n 的全排列,要求所产生的任一数字序列中不允许出现重复的数字。

#include
using namespace std;
int a[10],v[10];
int n;
void dfs(int x){
    if(x==n+1){
        for(int i=1;i<=n;i++) printf("%5d",a[i]);
        cout<<endl;
        return;
    }
    for(int i=1;i<=n;i++){
        if(!v[i]){
            a[x]=i;
            v[i]=1;
            dfs(x+1);
            v[i]=0;
        }
    }
}
int main()
{
    cin>>n;
    dfs(1);
}

2.实现指数型枚举

指数型,对于每一个要素有选与不选两种,构成2^n种状态。

洛谷-临时抱佛脚

描述
工作需要完成多个习题册,一项习题集由多个题目组成。如果可以同时做一项习题册两个题目,问做完最少时间是多少。
思路
对于一项习题册,枚举每一道题目,做还是不做,如果做的习题的累计加大于习题册的1/2,那么可能就是做完该习题册的答案,取最小的。

#include
using namespace std;
int a[5][22],len[5],minn,ans;
void dfs(int x,int sum,int index){
    if(x==len[index]+1){
        if(sum>=(a[index][0]+1)/2){
            //printf("index=%d sum=%d\n",index,sum);
            minn=min(sum,minn);
        }
        return;
    }
    dfs(x+1,sum,index);//不加入

    dfs(x+1,sum+a[index][x],index);//加入

}
int main()
{
    for(int i=1;i<=4;i++) cin>>len[i];
    for(int i=1;i<=4;i++){
        for(int j=1;j<=len[i];j++){
            cin>>a[i][j];
            a[i][0]+=a[i][j];
        }
    }
    for(int i=1;i<=4;i++){
        minn=a[i][0];
        dfs(1,0,i);
        //cout<
        ans+=minn;
    }
    cout<<ans<<endl;
}

洛谷-自然数拆分

#include
using namespace std;
#define N 10
int n,a[N],cnt;
void dfs(int x,int sum){
    if(x==n) return;
    if(sum>=n){
        if(sum==n){
            for(int i=1;i<cnt;i++) cout<<a[i]<<'+';
            cout<<a[cnt]<<endl;
        }
        return;
    }

    a[++cnt]=x;
    dfs(x,sum+x);
    cnt--;

    dfs(x+1,sum);
}
int main()
{
    cin>>n;
    dfs(1,0);
}

3.递归实现组合型枚举

洛谷-选数

题目描述
已知 n 个整数 x1,x2,x3……xn ,以及整数 k(k 思路
组合型枚举C(n,k)与指数型枚举2^n实现相似,截取前k个.

#include
using namespace std;
#define N 10
int n,m,res[N],a[N],cnt,ans;
bool check(int x){
    for(int i=2;i<=sqrt(x);i++){
        if(x%i==0) return false;
    }
    return true;
}
void dfs(int x,int num,int sum){//是否选数a[x],已经选的数目num,已选数的和sum
    //cout<
    if(x==n+1||num==m){//达到上限或者达到组合数目暂停
        if(num==m){
            if(check(sum)) ans++;
        }
        return;
    }

    //选
    dfs(x+1,num+1,sum+a[x]);

    //不选
    dfs(x+1,num,sum);
}
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>a[i];
    dfs(1,0,0);
    cout<<ans;
}

DFS深度优先搜索

应用:走迷宫,图的遍历、树的前中后遍历、可达性统计等。

搜索的过程:判断、打标记、记录、回溯

洛谷取数游戏

如何打标记,如何下一步搜索。
题目描述
一个N × M 的由非负整数构成的数字矩阵,你需要在其中取出若干个数字,使得取出的任意两个数字不相邻(若一个数字在另外一个数字相邻8个格子中的一个即认为这两个数字相邻),求取出数字和最大是多少。
思路
遍历的方式从第一行第一列开始,从左向右,到最后一列时然后增加行。每遍历一个数,标记周围八个(记得回溯的时候取消标记)。重点是如何标记?标识仅用0,1是不足够的,因为如果当前回溯取消标记,但有可能,在其它取数的周围,所以使用累加标记。

#include
using namespace std;
#define N 10
int n,m,num,maxx,t;
int g[N][N],v[N][N];
//标记:n个其他块覆盖到该块赋值为n,如果撤销一块那么该值减一
//如果用传统 0,1无法做到
int dx[8]={-1,-1,-1,0,0,1,1,1},
    dy[8]={-1,0,1,-1,1,-1,0,1};//方向
void dfs(int x,int y){
    if(x>=n+1){
        maxx=max(maxx,num);
        return;
    }
    if(y>=m+1){
        dfs(x+1,1);
        return;
    }
    dfs(x,y+1);//不选时
    if(!v[x][y]){//选时
        v[x][y]++;
        for(int i=0;i<8;i++) v[x+dx[i]][y+dy[i]]++;
        num+=g[x][y];
        dfs(x,y+1);

        v[x][y]--;//回溯
        for(int i=0;i<8;i++) v[x+dx[i]][y+dy[i]]--;
        num-=g[x][y];
    }
}
int main()
{
    cin>>t;
    while(t--){
        cin>>n>>m;
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                cin>>g[i][j];
            }
        }
        memset(v,0,sizeof(v));
        num=0,maxx=0;
        dfs(1,1);
        cout<<maxx<<endl;
    }
}

洛谷八皇后 如何检查?

一个如下的 6×6 的跳棋棋盘,有六个棋子被放置在棋盘上,使得每行、每列有且只有一个,每条对角线(包括两条主对角线的所有平行线)上至多有一个棋子。
【搜索】洛谷官方题单刷题总结~递归与枚举~DFS~BFS_第1张图片
求解出多少种方式?只需要按行号1~n的顺序输出列号。

思路:按照1~n的全排列输出的列号,可以满足每行、每列有且仅有一个。
例如n=3全排列 {2,1,3} 则三个皇后的位置 (1,2)、(2,1)、(3,3)
题目要求对角线上有且仅有一个,如何判别对角线上是否有皇后?对于同一个对角线上的元素左上到右下的对角线一定满足y2-x2+n == y1-x1+n, 从右上到左下的对角线满足x1+y1 == x2+y2

#include
using namespace std;
#define N 30
int res[N];
int n,ans,tot;
int row[N*2],col[N*2],v[N];
//v数组代表 该列号是否被访问
//row代表斜\对角线方向, col 代表/对角线方向
void dfs(int x){//输出列号y1,y2,y2,坐标依次为(1,y1)、(2,y2)、(3,y3)

    if(x==n+1){
        tot++;
        if(tot<=3){
            for(int i=1;i<=n;i++) cout<<res[i]<<' ';
            cout<<endl;
        }
        return;
    }
    for(int y=1;y<=n;y++){
        if(!v[y]&&!row[y-x+n]&&!col[x+y]){
            v[y]=1;
            row[y-x+n]=1;
            col[x+y]=1;
            res[x]=y;
            dfs(x+1);

            v[y]=0;//回溯
            row[y-x+n]=0;
            col[x+y]=0;
        }
    }
}

int main()
{
    cin>>n;
    dfs(1);
    cout<<tot<<endl;
}

洛谷-USACO-湖泊数量

题目描述
开局一张格子图,格子上可能是水洼W或者旱地.,如果水洼的周围八个点有水洼,则将他们是为一个整体(湖泊)。问有多少个湖泊?
题目思路
DFS搜索题模板,遍历每一个格子,如果该格子是水洼且没有被标记,答案数量增加1,从该格子X遍历,将所有相连是水洼的格子Y的标记(就是再遍历到Y不再计数)。
好熟悉的一道题,第一次做这道题时应该是在大一,当时ACM选修课每周天进行比赛遇到这道题,当时学了算法也一知半解,不会用。记得当时还一个下午都在模拟这道题,WA了很多次。

#include
using namespace std;
#define N 110
int n,m,v[N][N],ans;
string s[N];
int dx[8]={-1,-1,-1,0,0,1,1,1},
    dy[8]={-1,0,1,-1,1,-1,0,1};
bool check(int x,int y)
{
    return x>=0&&x<n&&y>=0&&y<m;
}
void dfs(int x,int y)
{
    //cout<
    for(int i=0; i<8; i++)
    {
        int xx=x+dx[i],yy=y+dy[i];
        if(check(xx,yy)&& !v[xx][yy]&&s[xx][yy]=='W')
        {
            v[xx][yy]=1;
            dfs(xx,yy);
        }
    }
}
int main()
{
    cin>>n>>m;
    for(int i=0; i<n; i++)cin>>s[i];
    for(int i=0; i<n; i++)
    {
        for(int j=0; j<m; j++)
        {
            if(s[i][j]=='W'&& !v[i][j]){
                    ans++;
                dfs(i,j);
            }
        }
    }
    cout<<ans<<endl;
}

洛谷-填涂颜色

题目描述
由数字0组成的方阵中,有一任意形状闭合圈,闭合圈由数字1构成。现要求把闭合圈内的所有空间都填写成2.
涂色前… … … … … … … 涂色后
【搜索】洛谷官方题单刷题总结~递归与枚举~DFS~BFS_第2张图片【搜索】洛谷官方题单刷题总结~递归与枚举~DFS~BFS_第3张图片
题目思路
类似于上道题目湖泊数量,遍历方法类似,不过这道题目只学要分辨那些在边缘上,那些被封闭。方阵四个边缘上遍历一遍,这样就能得到所有未封闭的方阵。

#include
using namespace std;
#define N 35
int n,m,v[N][N],ans;
int a[N][N];
int dx[4]={-1,0,0,1},
    dy[4]={0,-1,1,1};//上左右下
bool check(int x,int y){
    return x>0&&x<=n&&y>0&&y<=n;
}
void dfs(int x,int y){
    for(int i=0; i<4; i++){
        int xx=x+dx[i],yy=y+dy[i];
        if(check(xx,yy)&& !v[xx][yy]&&a[xx][yy]==0){
            v[xx][yy]=1;
            dfs(xx,yy);
        }
    }
}
void search_(int x,int y){
    if(!v[x][y]&&a[x][y]==0){
            v[x][y]=1;
            dfs(x,y);
    }
}
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            cin>>a[i][j];
        }
    }
    for(int i=0; i<n; i++){
        search_(1,i);
        search_(n,i);
        search_(i,1);
        search_(i,n);
    }
    //cout<
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            if(a[i][j]) cout<<1<<' ';
            else if(!a[i][j]&&v[i][j]) cout<<0<<' ';
            else cout<<2<<' ';
        }
        cout<<endl;
    }
}

洛谷单词方阵

题目描述
给出字符串方阵,类似于五子棋,寻找横向、纵向、或斜线方向构成yizhong六个字符串,其他字符输出为*;
题目思路
这道题用模拟也可以做,用搜索的话可能写的比较少。

#include
using namespace std;
#define N 110
int n,m,v[N][N],ans;
string s[N];
int dx[8]={-1,-1,-1,0,0,1,1,1},
    dy[8]={-1,0,1,-1,1,-1,0,1};
bool check(int x,int y){
    return x>=0&&x<n&&y>=0&&y<n;
}
string s1="yizhong";
pair<int,int> q[10];//暂存坐标
void dfs(int x,int y,int p,int num){//x,y代表坐标,p代表搜索方向,num代表遍历过的数量
    //cout<
    if(num>=6){
        for(int i=0;i<=num;i++) v[q[i].first][q[i].second]=1;
        return;
    }
    int xx=x+dx[p],yy=y+dy[p];
    if(check(xx,yy)&&s[xx][yy]==s1[num+1]){
        q[num+1].first=xx,q[num+1].second=yy;
        dfs(xx,yy,p,num+1);
    }
}
int main()
{
    cin>>n;
    for(int i=0;i<n;i++) cin>>s[i];
    for(int i=0;i<n;i++){
        for(int j=0;j<n;j++){
            if(s[i][j]=='y'){
                for(int k=0;k<8;k++){
                    q[0].first=i,q[0].second=j;
                    dfs(i,j,k,0);
                }
            }
        }
    }
    for(int i=0;i<n;i++){
        for(int j=0;j<n;j++){
            if(!v[i][j]) cout<<'*';
            else cout<<s[i][j];
        }
        cout<<endl;
    }
}

BFS广度优先搜索

应用:计算最小步数、树的层次遍历、最短路、拓扑排序。

洛谷-马的遍历

题目描述
有一个 n×m 的棋盘,在某个点 (x, y) 上有一个马,要求你计算出马到达棋盘上任意一个点最少要走几步。
思路
使用两个队列,一个用于记录坐标,一个记录步数。

#include
using namespace std;
int n,m,x,y,sx,sy,val;

queue<pair<int,int> >q;
queue<int>d;
int dis[410][410],v[410][410];
int dx[8]={-1,-2,-2,-1,1,2,2,1},
    dy[8]={-2,-1,1,2,-2,-1,1,2};
bool check(int x,int y){
    if(x<=0||x>n||y<=0||y>m) return false;
    return true;
}
int main()
{
    cin>>n>>m>>sx>>sy;
    memset(dis,-1,sizeof(dis));
    q.push(make_pair(sx,sy));
    d.push(0);
    v[sx][sy]=1;
    while(!q.empty()){
        x=q.front().first,y=q.front().second;
        q.pop();
        val=d.front();d.pop();
        dis[x][y]=val;
        for(int i=0;i<8;i++){
            int xx=x+dx[i],yy=y+dy[i];
            if(!v[xx][yy]&&check(xx,yy)){
                q.push(make_pair(xx,yy));
                d.push(val+1);
                v[xx][yy]=1;
            }
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            printf("%-5d",dis[i][j]);
        }
        cout<<'\n';
    }
}

洛谷-奇怪的电梯

题目描述
电梯的第i层只能上行a[i]i+a[i]层,或者下行a[i]i-a[i]。问从第A层出发最少多少次操作(上行 或者 下行 为一次操作)能够到达B层。
思路
最少步数一般使用BFS就可以了,需要注意一下细节就行了。

#include
using namespace std;
#define N 210
int n,m,x,y,A,B;
int a[N],dis[N],v[N];
queue<pair<int,int> >q;

bool check(int x){
    if(x<=0||x>n) return false;
    return true;
}
int main()
{
    cin>>n>>A>>B;
    for(int i=1;i<=n;i++) cin>>a[i];
    memset(dis,-1,sizeof(dis));
    memset(v,0,sizeof(v));
    v[A]=1;
    dis[A]=0;
    q.push(make_pair(0,A));
    while(!q.empty()){
        x=q.front().second,y=q.front().first;
        q.pop();
        //printf("->%d:%d\n",x,y);
        //printf("x+a[x]=%d : v[x+a[x]]=%d\n",x+a[x],v[x+a[x]]);
        //printf("x-a[x]=%d : v[x-a[x]]=%d\n",x-a[x],v[x-a[x]]);

        if(check(x+a[x])&& !v[x+a[x]]){
            v[x+a[x]]=1;
            dis[x+a[x]]=y+1;
            q.push(make_pair(y+1,x+a[x]));
        }
        if(check(x-a[x])&& !v[x-a[x]]){
            v[x-a[x]]=1;
            dis[x-a[x]]=y+1;
            q.push(make_pair(y+1,x-a[x]));
        }
    }
    cout<<dis[B]<<endl;
}

洛谷Meteor_Shower_S

题目描述
流星雨席卷农场,流星雨会坠t时刻落到一个格子(x,y),同时会将周围四个格子及坠落的格子烧焦,无法行走(t时刻及t时刻以后)。主角从(0,0)出发,经过没有被烧焦的格子,到达安全地方最少时间。
思路,题目问最少时间,基本可以确定是广搜BFS题目,本题特点是要处理流星坠落的数据,某个格子是否不会受到流星侵袭,也不会被烧焦,或者在什么时刻被烧焦?在搜索的的过程中:
1.需要判断是否到达了安全格子,到达立刻结束返回步数;
2.某个格子是否能经过。
3.如果流星雨规模过于集中,主角无路可逃,即搜索结束也没能找到安全格子,输出-1

坑点:主角可以逃到格子之外,但x>=0&&y>=0,是否判断(0,0)是安全的?或者开局成盒?

#include
using namespace std;
#define N 610
int dx[5]={0,-1,0,0,1},//中上左右下
    dy[5]={0,0,-1,1,0};
int g[N][N],v[N][N];
int x,y,t,m;
queue<pair<int,int> >q;//保存坐标
queue<int>d;//保存步数

bool check(int x,int y){
    if(x<0||y<0) return false;
    return true;
}

int main()
{
    memset(g,-1,sizeof(g));
    cin>>m;
    for(int i=1;i<=m;i++){
        cin>>x>>y>>t;
        for(int j=0;j<=4;j++){
            int xx=x+dx[j],yy=y+dy[j];
            if(check(xx,yy)&&(g[xx][yy]==-1||g[xx][yy]>t)){
                g[xx][yy]=t;
            }
        }
    }
    if(g[0][0] == -1){
        cout<<0<<endl;
        return 0;
    }else if(g[0][0]==0){
        cout<<-1<<endl;
        return 0;
    }
    q.push(make_pair(0,0));
    d.push(0);
    v[0][0]=1;
    while(!q.empty()){
        x=q.front().first,y=q.front().second;
        q.pop();
        t=d.front();
        d.pop();
        for(int i=1;i<=4;i++){
            int xx=x+dx[i],yy=y+dy[i];
            if(check(xx,yy)&& !v[xx][yy]){
                if(g[xx][yy]>t+1){
                    v[xx][yy]=1;
                    q.push(make_pair(xx,yy));
                    d.push(t+1);
                }else if(g[xx][yy]==-1){
                    cout<<t+1<<endl;
                    return 0;
                }
            }
        }
    }
    cout<<-1<<endl;
}

你可能感兴趣的:(#,搜索,搜索)