蓝桥杯c++b组练习题自用记录3

搜索DFS、BFS

题目:N皇后问题

剪枝技巧在这个程序中体现在 PD 函数和 DFS 函数中。

  1. PD 函数中的剪枝: 在 PD 函数中,每次放置一个皇后时,会检查该皇后与之前已放置的皇后是否存在冲突。如果存在冲突,则返回 0,表示不能放置在当前位置,从而减少了不必要的递归。这样,就避免了在已经冲突的情况下继续递归搜索,提高了程序的效率。

  2. DFS 函数中的剪枝: 在 DFS 函数中,首先会调用 check 函数检查是否已经放置了 n 个皇后,如果是,则符合条件,直接返回。这样可以避免在已经放置了 n 个皇后之后继续递归搜索,节省了不必要的计算时间。

此外,在 DFS 函数中,还会在放置皇后之前进行判断,如果当前位置不符合条件(即调用 PD 函数返回 0),则直接跳过当前列的放置,继续尝试下一列,从而减少了搜索空间,提高了效率。

这些剪枝技巧使得程序能够更加高效地搜索出符合条件的解,避免了不必要的计算和搜索,从而提高了程序的运行效率。

#include<bits/stdc++.h>
using namespace std;

int x[15]={0};//x[i] 表示第 i 个皇后所在的列号
int sum,n;
//n皇后冲突的条件是:
//在同一条对角线上(行号之差的绝对值等于列号之差的绝对值)
//在同一列上
int PD(int k){
	for(int i=1;i<k;i++){
		if(abs(k-i)==abs(x[k]-x[i]))
			return 0;
		else if(x[k]==x[i])//在同一列上
			return 0;
	}
	return 1;
} 
bool check (int a){
	if(a>n)	{	//满足条件 ,可以退回main输出结果 
		sum++;	//检查是否已经放置了 n 个皇后 
		return 1;
	}		
	else		
		return 0;	
} 

void DFS(int a){
	if(check(a))
		return ;
	else
		for(int i=1;i<=n;i++){
			x[a]=i;//第a个皇后放的列数
		if(PD(a))//判断是否能放这步
			DFS(a+1);//能就进行下一个 
		else continue; //不能就下一列 
		}
} 
int main(){
	cin>>n;//n个皇后 
	DFS(1);//每次都从第一个皇后开始 
	cout<<sum<<endl;
	return 0;
}

题目:最大数字

#include<bits/stdc++.h> // 包含 C++ 标准库的所有头文件
using namespace std; // 使用标准命名空间

string n; // 输入的数字序列
long long ans; // 最终的最大数
int a,b; // 操作1和操作2的剩余次数

// 深度优先搜索函数,寻找最大数
void dfs(int x, long long an){
    int t = n[x] - '0'; // 将字符转换为数字

    // 如果当前处理的数字位存在
    if(n[x]){
        // 处理操作1
        int c = min(a, 9 - t); // 计算当前位置可以进行的操作1的最大增量
        a -= c; // 更新操作1的剩余次数
        dfs(x + 1, an * 10 + t + c); // 递归处理下一位数字
        a += c; // 恢复操作1的剩余次数

        // 如果剩余的操作2次数大于当前数字
        if(b > t){
            b = b - t - 1; // 更新操作2的剩余次数
            dfs(x + 1, an * 10 + 9); // 递归处理下一位数字
            b = b + t + 1; // 恢复操作2的剩余次数
        }
    } else {
        ans = max(ans, an); // 更新最大数
    }
}

int main(){
    cin >> n >> a >> b; // 输入数字序列和操作次数
    dfs(0, 0); // 从数字的第一位开始进行深度优先搜索
    cout << ans; // 输出最大数
    return 0;
}

在此之前,我还有一种写法,但是只通过了60%的测试数据,我先做个记录

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

char c[20];
ll ans=0;
int n,m;//n:1号操作剩余次数  m:2号操作剩余次数

void dfs(int i,ll v){
	int x=c[i]-'0';//将字符 c[i] 转换为对应的数字
	if(c[i]){//判断用于检查当前处理的数字位是否存在,则不为结束符 \0 时 
		int t=min(n,9-x);//计算了当前位置可以进行操作1的最大增量,确保当前数字加上增量后不会超过 9
		n=n-t;//更新剩余操作次数 n
		dfs(i+1,v*10+x+t);//进行操作1,递归调用 dfs 函数,处理下一位数字,并将得到的新数字加入当前数字的十位
		n=n+t;//在递归调用 dfs 函数之后,恢复剩余操作次数 n,因为递归调用结束后,需要回溯到当前状态。
		
		//判断用于检查当前位置是否可以进行操作2,当前数字大于 0 时
		if(m>x){
			m=m-x+1;//更新剩余操作次数 m
			dfs(i+1,v*10+9);//进行操作2,将当前数字减去尽可能大的数(即减到 9),并递归调用 dfs 函数,处理下一位数字。
			m=m+x+1;//
		}
	}
	else{//已经处理完所有数字,不需要再进行递归 
		ans=max(ans,v);//更新最大值并返回
	}	
}

int main(){
	scanf("%s%d%d",c,&n,&m);
	dfs(0,0);
	printf("%lld",ans);
	return 0;
} 

题目:路径之谜

此题我写的时候,测试用例7个只通过了6个,通过率85.7%,但是我觉得这份代码是思路比较好理解dfs,时间也比较紧迫,所以坚持不改了。有需要的可以上蓝桥杯官网上看100%通过的题解。

#include<bits/stdc++.h>
using namespace std;
//#define ll long long

struct PII{
	int first;
	int second;
};

const int N = 30;//最大的网格大小
int rol[N];//行 
int col[N];//列 
int n;//格子数 长款从1到n
bool flag[N][N];//标记网格中的位置是否已经走过
vector<PII> res; //存储路径的容器,每个节点表示一个格子的位置

//------------------------------图的路径搜索常用方向移动表示 
int dx[4]={0,1,-1,0};// 定义四个方向的横坐标增量
int dy[4]={1,0,0,-1};// 定义四个方向的纵坐标增量

//两两组合形成上下左右四个方向
//		1-----------------x 
//		|
//		|
//		|
//		|
//		y
//
//dx[0]=0 dy[0]=1//向下
//dx[0]=0 dy[0]=-1//向上
//dx[0]=-1 dy[0]=0//向左
//dx[0]=1 dy[0]=0//向右 
//-----------------------------------------------------------
// 检查当前位置是否为终点
bool check(int x,int y){
	if(x==n&y==n) {// 如果当前位置为右下角
			for (int i=1;i<=n;i++){
				if(rol[i]!=0)// 如果有任何一行的靶子数不为0,说明不是有效路径
					return false;
			}
			for(int i=0;i<res.size();i++){
				int x=res[i].first;//获取当前节点的x轴坐标 
				int y=res[i].second;//获取当前节点的y轴坐标
				int sum=n*(x-1)+y-1;//计算当前节点在一维数组中的位置
				cout<<sum<<" ";
			}
			cout<<endl;
			return false;//到达右下角,成功终止 
	}
	return true;//继续搜索 
} 

bool pd(int x2,int y2){//边界判断 
	//已经被走过,不能再走,超出边界
	if(flag[x2][y2]==1) 
		return 0;
	//从左侧走出方格 
	else if(x2<1) 
		return 0;
	//从右侧走出方格 
	else if(x2>n) 
		return 0;
	//从上侧走出方格 
	else if(y2<1) 
		return 0;
	//从下侧走出方格 
	else if(y2>n) 
		return 0;
	//没走到右下角,箭用完了
	else if(col[x2]<=0)
		return 0; 
	else if(rol[y2]<=0)
		return 0; 
	else return 1;//符合边界条件,可以继续执行搜索 
}

void dfs(int x,int y){
	if(!check(x,y)){// 如果当前位置不满足条件
		return ;//回溯,用于剪枝 
	}
	else {
		for(int i=0;i<4;i++){ // 遍历四个方向
			int xt=dx[i]+x;// 计算下一个节点的横坐标
			int yt=dy[i]+y;// 计算下一个节点的纵坐标
			if(!pd(xt,yt)){// 如果下一个位置不满足边界条件
				continue;// 继续尝试下一个方向
			}
			else{
				flag[xt][yt]=true;// 标记当前位置已经访问过
				col[xt]--; //更新列靶子数 
				rol[yt]--;//更新行靶子数 
				res.push_back({xt,yt});// 将当前位置加入路径中
				dfs(xt,yt);// 递归搜索下一个节点
				res.pop_back();// 回溯,撤销当前位置的访问
				flag[xt][yt]=false;// 回溯,恢复当前位置的未访问状态
				col[xt]++;//回溯,恢复列靶子数 
				rol[yt]++;//回溯,恢复行靶子数
			}
		} 
	} 
} 

int main(){
	cin>>n;// 读取格子数
	for(int i=1;i<=n;i++)
		cin>>rol[i];//行的靶子数 
	for(int i=1;i<=n;i++)
		cin>>col[i];//列的靶子数 
	flag[1][1]==true;// 标记起点已经访问过
	col[1]--;//更新靶子数 
	rol[1]--;
	res.push_back({1,1});// 将起点加入路径中
	dfs(1,1);// 开始深度优先搜索
	return 0;
} 

题目:长草

#include <bits/stdc++.h>
using namespace std;

const int M = 1005;

struct PII
{
    int first;//x
    int second;//y
};
// C++ 有个数据类型叫 pair 上面的就可以定义为 pair<int,int> 用起来比较方便。

PII tempPair;//临时结点
char Map[M][M];// 地图,存储地块信息

//---------图的路径搜索常用方向移动表示-------
int dx[4]= {0,1,-1,0};
int dy[4]= {1,0,0,-1};
// 两两组合形成上下左右四个方向
//      1------------------> x
//      |
//      |
//      |
//      |
//      |
//      |
//      |
//      ↓
//      y

// dx[0]=0 dy[0]=1 那么代表向下的方向
// dx[1]=1 dy[1]=0 那么代表向右的方向
// dx[2]=-1 dy[2]=0 那么代表向左的方向
// dx[3]=0 dy[3]=-1 那么代表向上的方向

int n;// n 行
int m;// m 列
int k;// k 次/个月 

queue<PII > q; //广度优先搜索所用的队列

int len;//记录节点数量方便后续k的计算
bool pd(int x, int y)
{
    if(x<1)
        return 0;
    // /x 轴坐标 左侧越界
    else if(x>n)
        return 0;
    //x 轴坐标 右侧越界
    else  if(y<1)
        return 0;
    //y 轴坐标 上侧越界
    else if(y>m)
        return 0;
    //y 轴坐标 下侧越界
    else if(Map[x][y]=='g')
        return 0;
    //已经长草了
    else return 1;
    // 在范围内,且没长草
}

void BFS()
{
    //BFS
    while(!q.empty()&&k>0)
    {
        tempPair = q.front();// 获取队首节点
        q.pop();// 弹出队首节点
        //这两步是取出队首的节点

        int x = tempPair.first;//获取横坐标
        int y = tempPair.second;//获取纵坐标
		// 遍历四个方向,上下左右 
        for(int i=0; i<4; i++)
        {
            int nowx = x+dx[i]; //扩展后的横坐标
            int nowy = y+dy[i]; //扩展后的纵坐标

            if(pd(nowx,nowy))
            {
                q.push({nowx,nowy});// 将满足条件的坐标加入队列
                Map[nowx][nowy]='g';// 标记为已长草
            }
            //符合要求执行扩展,不符合要求,忽略即可。
        }

        len--; //每取出一个节点 len-1
        if(len==0)
        {
            //当len =0 时,代表当前层扩展完了,那么就代表第一个月扩展完了
            k--; // 所以k--
            len = q.size(); // 当前层扩展完了,那就该扩展下一层了,所以len又被赋值为下一层的节点数目的值
        }
    }
}
int main()
{
    cin>>n>>m;// 输入行数和列数
    for(int i=1; i<=n; i++)
    {
        for(int j=1; j<=m; j++)
        {
            cin>>Map[i][j];// 输入地块信息
            if(Map[i][j]=='g')
            {
                tempPair.first=i;
                tempPair.second=j;
                q.push(tempPair);//将初始有树的结点加入队列
            }
        }
    }

    len = q.size();//记录第一层的节点数量方便后续k的计算
    cin>>k;//输入月数/次数 
    BFS();
    for(int i=1; i<=n; i++)
    {
        for(int j=1; j<=m; j++)
        {
            cout<<Map[i][j];// 输出结果地图
        }

        cout<<endl;
    }
    return 0;
}

题目:走迷宫

#include<bits/stdc++.h> // 包含常用的标准库头文件

using namespace std; // 使用标准命名空间

int vis[150][150]; // 用于存储是否访问过,并且存储长度
char G[150][150]; // 用于存储题目给出的地图
int n,m,ans=0; // n、m分别表示地图的行数和列数,ans用于存储最终结果

int dx[4]={0,0,-1,1}; // 方向数组,表示四个方向的x坐标变化
int dy[4]={1,-1,0,0}; // 方向数组,表示四个方向的y坐标变化

struct node{ // 定义结构体node,表示坐标点
    int x;
    int y;
};
 
node Start,End; // 分别表示起点和终点的坐标

bool pd(int x,int y){ // 判断坐标(x,y)是否符合条件
    if(x<1) 
        return 0;
    else if(x>n)    
        return 0;
    else if(y<1)    
        return 0;
    else if(y>m)    
        return 0;
    else if(vis[x][y]!=0) // 已经访问过了 
        return 0;
    else if(G[x][y]!='1') // 不是路不能走 
        return 0; 
    else 
        return 1;
} 

bool check(int x,int y){ // 判断是否到达终点
    if(x==End.x&&y==End.y){    // 找到终点,把距离给他 
        ans=vis[x][y];
        return 1;
    }
    else 
        return 0;
} 

void bfs(){ // 广度优先搜索
    queue<node>q; // 定义队列q,用于存储待访问的节点
    node now,next; // 定义当前节点和下一个节点
    q.push(Start); // 将起点压入队列中
    vis[Start.x][Start.y]=1; // 标记起点已经访问
    while(!q.empty()){ // 当队列不为空时循环
        now = q.front();    
        if(check(now.x,now.y)) // 判断是否到达终点
            return ;    
        q.pop(); // 将队列最前面的弹出
        for(int i=0;i<4;i++){ // 遍历四个方向
            int nextx = now.x+dx[i];
            int nexty = now.y+dy[i];
            if(pd(nextx,nexty)){    // 判断下一个节点是否符合条件 
                next.x=nextx;
                next.y=nexty;
                q.push(next); // 将下一个节点压入队列中
                vis[nextx][nexty] = vis[now.x][now.y]+1; // 步数+1 
            }
        }   
    }
}

int main(){
    cin>>n>>m; // 输入地图的行数和列数
    for(int i=1;i<=n;i++){ // 输入地图信息
        for(int j=1;j<=m;j++){
            cin>>G[i][j];
        }
    }
    cin>>Start.x>>Start.y>>End.x>>End.y; // 输入起点和终点坐标
    ans=0;
    bfs(); // 调用广度优先搜索算法
    cout<<ans-1<<endl; // 输出结果
    return 0;
}

题目:迷宫

此题用BFS搜索记录路径,用DFS打印路径

#include<bits/stdc++.h> 
using namespace std; 
#define maxn 2000// 定义迷宫大小

string maze[maxn]={// 迷宫地图,0代表可通行,1代表墙壁
"01010101001011001001010110010110100100001000101010",
"00001000100000101010010000100000001001100110100101",
"01111011010010001000001101001011100011000000010000",
"01000000001010100011010000101000001010101011001011",
"00011111000000101000010010100010100000101100000000",
"11001000110101000010101100011010011010101011110111",
"00011011010101001001001010000001000101001110000000",
"10100000101000100110101010111110011000010000111010",
"00111000001010100001100010000001000101001100001001",
"11000110100001110010001001010101010101010001101000",
"00010000100100000101001010101110100010101010000101",
"11100100101001001000010000010101010100100100010100",
"00000010000000101011001111010001100000101010100011",
"10101010011100001000011000010110011110110100001000",
"10101010100001101010100101000010100000111011101001",
"10000000101100010000101100101101001011100000000100",
"10101001000000010100100001000100000100011110101001",
"00101001010101101001010100011010101101110000110101",
"11001010000100001100000010100101000001000111000010",
"00001000110000110101101000000100101001001000011101",
"10100101000101000000001110110010110101101010100001",
"00101000010000110101010000100010001001000100010101",
"10100001000110010001000010101001010101011111010010",
"00000100101000000110010100101001000001000000000010",
"11010000001001110111001001000011101001011011101000",
"00000110100010001000100000001000011101000000110011",
"10101000101000100010001111100010101001010000001000",
"10000010100101001010110000000100101010001011101000",
"00111100001000010000000110111000000001000000001011",
"10000001100111010111010001000110111010101101111000"
};
bool vis[maxn][maxn];// 标记数组,标记是否访问过某个位置
int dir[4][2]={{1,0},{0,-1},{0,1},{-1,0}};//D L R U

bool in(int x,int y){// 判断坐标是否在迷宫范围内
	return x<30&&x>=0&&y>=0&&y<50;
} 

struct node{
	int x,y,d;// x,y表示坐标,d表示步数
	char pos;//存储路径信息:D L R U 
};

node father[maxn][maxn];//当前节点的父节点
node now,nex;//指向当前和下一个位置

void dfs(int x,int y){	//递归打印 
	if(x==0&&y==0)	//找到起点 开始正向打印路径
		return;
	else
		dfs(father[x][y].x,father[x][y].y);// 递归打印父节点
	cout<<father [x][y].pos;// 打印当前节点的移动方向
} 
void bfs(int x,int y){
	queue<node> q;
	
	now.x=x;
	now.y=y;
	now.d=0;
	q.push(now);// 将起点加入队列
	vis[x][y]=true;// 标记起点已访问
	while(!q.empty()){
		now=q.front(); // 取出队首节点
		q.pop();
		for(int i=0;i<4;i++){
			int tx=now.x+dir[i][0];// 计算下一个位置的坐标
			int ty=now.y+dir[i][1];
			if(in(tx,ty)&&!vis[tx][ty]&&maze[tx][ty]!='1'){// 判断下一个位置是否合法
				vis[tx][ty]=true;// 标记下一个位置已访问
				nex.x=tx;
				nex.y=ty;
				nex.d=now.d+1;// 步数加一
				q.push(nex);//将下一个节点压入队列
				father[tx][ty].x=now.x; 
				father[tx][ty].y=now.y; 
				if(i==0)//存储路径 
					father[tx][ty].pos='D';
				else if(i==1)
					father[tx][ty].pos='L';
				else if(i==2)
					father[tx][ty].pos='R';
				else if(i==3)
					father[tx][ty].pos='U';		
			}
		}
	}
	
} 

int main(){
	bfs(0,0);
	dfs(29,49);//30行50列,从0开始 
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值