24上半年ACM第六次周赛题解

文章详细介绍了深度优先搜索(DFS)在排列数字和八皇后问题中的应用,以及广度优先搜索(BFS)在马的遍历和奇怪的电梯问题中的运用。同时,还讨论了优化时间和剪枝在搜索算法中的重要性,如在搜索(二)中的应用。
摘要由CSDN通过智能技术生成

目录

A.排列数字

B.八皇后

C.马的遍历

D.奇怪的电梯

E.搜索(二)


 

A.排列数字

这是一道典型的的DFS模板题,将所有数字的排列输出。当n=3时,以u=0第一次进入dfs函数,u的作用就是来判断在第几层递归,首先是第0层进入循环体,在第0层的第一次循环中将1放到第一位上并对1做上标记,然后开始递归到第一层,在第一层递归中的第一次循环因为1被标记,固只能在第二次循环时将2放到第二位上并对其做标记,然后递归到第二层,在第二层的递归中的第一和二次循环中1和2都被标记,只能在第三次循环中将3放到第三位上并对其标记,然后递归到第三层,在第三层递归中因为u=3达到了输出的条件,然后将存入bj数组的数输出。这时已经完成了一次的输出然后开始回溯,从第三层递归开始回溯到第二层并将3取消标记,然后再次回溯到第一层循环将2取消标记,因为在第一层的第二次循环就开始递归,当重新回溯时就到了第一层的第三次循环,将3放到第二位上,然后再次进入第二层的递归,将2放到第三位,然后又进入第三层递归,将新存入的数输出,以此类推将所有的排列组合输出。

#include<bits/stdc++.h>

using namespace std;

const int N=1e5+10;



int n;

int a[N],bj[N];

bool b[N];



void dfs(int u)//u来判断在第几层的递归,u从零开始方便与n对应

{

    if(u==n)//u到第n层递归时,输出存入的数

    {

        for(int i=0;i<n;i++)

        {

            cout<<bj[i]<<' ';

        }

        cout<<"\n";

    }

    for(int i=0;i<n;i++)//每一层的递归都要将所有的数遍历一遍

    {

        if(b[a[i]]==false)//遍历到没有标记的数才进入

        {

            bj[u]=a[i];//将进入的数存入bj数组

            b[a[i]]=true;//将进入的数打上标记

            dfs(u+1);//进入下一层递归

            b[a[i]]=false;//取消标记

        }

    }

}





int main()

{  

    cin>>n;

    for(int i=0;i<n;i++)a[i]=i+1;//将1到n的书存入a数组中

    dfs(0);//开始搜索

    return 0;

}

B.八皇后

这也是一道经典的DFS的模板,它需要通过一些简单的数学知识来判断每一行,每一列,每个对角线和反对角线是否只存在一个皇后,通过三个数组来判断每一列,对角线和反对角线是否只存在一个,当符合条件时,存入bj数组,最后递归到n时输出每一行的皇后的位置,再加一个标记用于只输出前三个样例

#include<bits/stdc++.h>

using namespace std;

const int N=10;





bool a[N],b[N],c[N];//a用于标记每一列,b用于标记每个对角线,c用于标记每个反对角线

int bj[N];//用于存储每一次的解

int n,x=0,p=0;//x用于存储解的个数,p用于判断只输出前三个解





void dfs(int u)

{

    if(u==n)

    {

        if(x<=2)

        {

            for(int i=0;i<n;i++)cout<<bj[i]<<' ';

            cout<<endl;

        }

        x++;//解的个数

    }

    for(int i=1;i<=n;i++)

    {

        if(!a[i]&&!b[u+i]&&!c[u-i+n])

        {

            a[i]=b[u+i]=c[u-i+n]=true;

            bj[u]=i;

            dfs(u+1);

            a[i]=b[u+i]=c[u-i+n]=false;
             

        }

    }

}



int main()

{

    cin>>n;

    dfs(0);

    cout<<x;

    return 0;

}

C.马的遍历

这是一道BFS的稍微提高一点的题,经典BFS是四个方向的遍历而这一道题是八个方向,先进行计算前的处理,将棋盘所有的点初始化为-1,以便于将没有到达的点输出为-1.然后将棋盘通过bool数组全部标记,以判断马是否走过。最后将初始点的标记取消,并将初始点的步数赋值为0。

 


#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> PII;//stl存储坐标x,y

//也可以用结构体

//struct point

//{

//    int,x,y;

//}
const int N=410;

int a[N][N];//用于存放从起始点到每个点的步数
bool b[N][N];//用于标记是否走过
int dx[8]={1,2,-1,-2,1,2,-1,-2},dy[8]={-2,-1,-2,-1,2,1,2,1};//存储马要走的八个方向



void bfs(int x,int y)
{
    queue<PII> q;//建一个队列用于存放符合条件的点
    q.push({x,y});//将初始点放入队列中
    while(q.size())//当队列中为空时结束循环
    {
        pair<int,int> p=q.front();//返回队列中的首个元素
        q.pop();//清掉第一个元素
        for(int i=0;i<8;i++)//遍历八个方向
        {
            int xx=p.first+dx[i],yy=p.second+dy[i];//新的点的坐标
            
            if(b[xx][yy]==true)//判断是否放入队列中
            {
                a[xx][yy]+=a[p.first][p.second]+2;//加2是为了先抵消初始化                                          //的-1,再在步数上加1
                b[xx][yy]=false;//标记走过的点
                q.push({xx,yy});//将符合条件的放入队列中
            }
        }
    }
}

int main()
{
    int n,m;
    cin>>n>>m;
    int x,y;
    cin>>x>>y;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            b[i][j]=true;
            a[i][j]=-1;
        }
    }
    a[x][y]=0;
    b[x][y]=false;//以上为初始化
    bfs(x,y);
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            printf("%-5d",a[i][j]);
        }
        cout<<endl;
    }
    return 0;
}

D.奇怪的电梯

这是一道BFS的提高题,刚开始数据比较弱DFS也能过,现在已经过不去了。这道主要还是考的BFS。首先电梯只有两个方向,所以和BFS的模板走迷宫和那个马的遍历有所不同,这只需要遍历两个方向,虽然这个方向减少了,但是它需要做许多的预处理。

#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef pair<int,int> PII;
const int N=2000;
 
int n,A,B;
int a[N],b[N];
int ans=0,a1,xx;
bool c[N];
 
void bfs()
{
    queue<PII> q;//这个pair时将你某一次的所在楼层和按按钮的次数绑定在一起
    q.push({A,0});//将起初的楼层和按了零次放入队列中
    c[A]=true;//将初始楼层标记防止再次回到该楼层,以输出最优的解
    int bj=0;//用于判断最后是否走到目标楼层
    while(q.size())
    {
        PII p=q.front();
        if(p.first==B)
        {
            cout<<0;
            return;
        }
        q.pop();
        for(int i=-1;i<2;i+=2)//遍历两个方向,该循环实质就是乘与1或-1
        {
            xx=p.first+a[p.first-1]*i;//乘1是向上,乘-1是向下,我写的数组下标从0开始的所以要 
                                      //减1
            if(xx>=1&&xx<=200&&c[xx]==false)
            {
                c[xx]=true;
                if(xx==B){bj=p.second+1;break;}//判断是否到达目标楼层,到达直接终止,优化时 
                                               //间复杂度
                q.push({xx,p.second+1});
            }
        }
        if(xx==B)break;//跳出条件
    }
    if(bj!=0)cout<<bj;//判断是否到达过目标楼层,到达则输出次数,否则输出-1
    else cout<<-1;
}
 
void solve()
{
    cin>>n>>A>>B;
    for(int i=0;i<n;i++)cin>>a[i];//输入楼层
    bfs();
}
 
 
 
signed main()
{
    int t;
    t=1;
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);//关闭数据流
    while(t--)
    {
        solve();
    }
    return 0;
}

E.搜索(二)

这是一道加强版的DFS的题目,需要判断许多条件。这一道题开始也是数据太弱,现在已经加强,还有一组特殊样例。现在数据加强后需要剪枝来优化时间复杂度,否则会TLE。特殊样例中有一个容易忽视的细节需要多观察观察。

#include<bits/stdc++.h>
using namespace std;
const int N=30;
int n,a[N],bj[N];
bool b[N];
int k;
int p=0,x1=0;
 
void dfs(int u)
{
    if(x1==k)//求和的数来跟目标k比较,若相同直接输出
    {
        cout<<"YES\n";
        for(int i=0;i<n;i++)cout<<a[i]<<' ';
        cout<<endl;
    }
    else if(x1<k)cout<<"NO\n";//若求和的数小于目标k,直接输出NO,因为每个数只能选一次,求和小 
                              //于目标k,无论如何都不可能成立
    if(p==1)return;//p用来标记,使只输出第一次成立的答案
    if(u==n)return;//若递归到最后也为成立,直接返回,优化时间
    int x=0;
    for(int i=0;i<u;i++)x+=bj[i];//求和来判断是否与目标k相同
    if(x==k&&u!=0)//这里需要特别注意,当目标k为 0 时,数组中没有数时,x的初始化为 0 ,就会误判
    {             //所以需要再加一个判断数组中必须有数
        cout<<"YES\n";
        for(int i=0;i<u;i++)cout<<bj[i]<<' ';//达到条件后输出第一次成立的数的组合
        cout<<endl;
        p++;
    }
    for(int i=0;i<n;i++)
    {
        if(b[a[i]]==false)//正常的dfs判断
        {
            bj[u]=a[i];
            b[a[i]]=true;
            dfs(u+1);
            b[a[i]]=false;
        }
    }
}
 
int main()
{
    cin>>n>>k;
    for(int i=0;i<n;i++)
    {
        cin>>a[i];x1+=a[i];//首先将所有数据输入求和
    }
    dfs(0);
    if(p==0)cout<<"NO\n";
    return 0;
}

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值