目录
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;
}