蓝桥杯备赛日记——DFS基础
1.DFS剪枝
OJ2942 数字王国之军训排队
思路
写一个dfs函数,这个dfs函数有两个参数,dep和i,dep表示第dep位同学,i表示打算把所有人分成i支队伍,这个函数的功能是来检测是否能把所有同学分成i支队伍,更加严谨的来说是检测能否把第dep位同学分到这i支队伍里面的某支队伍里面,如果能分,(先把它塞进能分进去的那支队伍里面(用vector数据结构)),那就看看下一位同学也就是第dep+1位同学能不能分到这i支队伍里面的某支队伍里面,如果此时dep==n+1就说明全部为同学是能够分为i支队伍的。
代码
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
//思路:dfs,这题的dfs函数的作用是试一试所有同学分成i队能不能成功,如果成功返回true
//重要的数据结构vector
const int N=11;
int n;
int a[N];
vector<int> v[N];//对vector数据结构了解还是比较浅,记得好好学习一下
bool dfs(int dep,int i)//dfs(dep,i)表示分配到第dep位同学能否分成i队
{
if(dep==n+1)return true;//达到n+1层就说明可以分成i队,返回true,表示能成
for(int j=1;j<=i;j++)//分别枚举分成的i只队伍里面各自队伍里所有的成员
{
bool flag=true;//先假设他能留在第j支队伍里面
for(const auto &x:v[j])//枚举第j支队伍里面的所有成员
{
if(a[dep]%x==0||x%a[dep]==0)//看看能不能被整除
{
flag=false;//如果能被整除说明第dep个同学不能留在第j支队伍里面
break;
}
}
if(flag==false)continue;//如果不能留在第j支队伍里面,看看下一支队伍能不能
v[j].push_back(a[dep]);//如果能那就把第dep位同学放在第j支队伍里面
if(dfs(dep+1,i))return true;//递归下一层
v[j].pop_back();//恢复现场
}
return false;
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=n;i++)
{
if(dfs(1,i))//如果能分成i队,那就输出i,跳出循环,这就是最少的队伍数量
{
cout<<i;
break;
}
}
return 0;
}
OJ1508 N皇后
思路
开一个数组vi[i][j],如果坐标[i][j]的vi[i][j]大于零就说明有皇后占用了。
写一个dfs函数,去搜索每一行的哪一列可以放置皇后,在一个位置放置完皇后后,的把他所在位置的米字型方向位置都用vi[i][j]++,表示位置已经被占用,然后去搜下一层(dfs(dep+1)),然后恢复现场vi[i][j]–;
代码
#include <iostream>
using namespace std;
const int N=12;
int vi[N][N];
int n,ans=0;
void dfs(int dep)//dep表示行
{
//跳出条件
if(dep==n+1)//如果递归搜索到n+1层就说明当前方案是一种可行方案
{
ans++;//方案数加加
return;//当前递归结束
}
for(int i=1;i<=n;i++)//枚举列
{
if(vi[dep][i])continue;//如果但前dep行i列有人占用的话就跳过
//改变状态
//当前[dep][i]所在的米字型都vi++
for(int _j=1;_j<=n;_j++)vi[dep][_j]++;//当前dep行所有列都被占用了
for(int _i=1;_i<=n;_i++)vi[_i][i]++;//当前位置一列都被占用了
for(int _i=dep,_j=i;_i>=1&&_j>=1;_j--,_i--)vi[_i][_j]++;//米字型左上角
for(int _i=dep,_j=i;_i>=1&&_j<=n;_j++,_i--)vi[_i][_j]++;//米字型右上角
for(int _i=dep,_j=i;_i<=n&&_j>=1;_j--,_i++)vi[_i][_j]++;//米字型左下角
for(int _i=dep,_j=i;_i<=n&&_j<=n;_j++,_i++)vi[_i][_j]++;//米字型右下角
dfs(dep+1);//递归搜索下一层
//恢复现场
for(int _j=1;_j<=n;_j++)vi[dep][_j]--;//当前dep行所有列都被占用了
for(int _i=1;_i<=n;_i++)vi[_i][i]--;//当前位置一列都被占用了
for(int _i=dep,_j=i;_i>=1&&_j>=1;_j--,_i--)vi[_i][_j]--;//米字型左上角
for(int _i=dep,_j=i;_i>=1&&_j<=n;_j++,_i--)vi[_i][_j]--;//米字型右上角
for(int _i=dep,_j=i;_i<=n&&_j>=1;_j--,_i++)vi[_i][_j]--;//米字型左下角
for(int _i=dep,_j=i;_i<=n&&_j<=n;_j++,_i++)vi[_i][_j]--;//米字型右下角
}
}
int main()
{
cin>>n;
dfs(1);//求结果
cout<<ans;
return 0;
}
OJ182小朋友崇拜圈
思路
用时间戳数组dfn[N]去记录每一个小朋友是第几个走到的,mindfn用来表示进入一个圆圈前走到的第一个点的时间戳。
写一个dfs函数,这个函数的参数是x,x表示当前走到的同学编号,走当前的x同学的时候,就给他记录一个时间戳,在判断它崇拜的同学之前是否走过,如果走过,那么可能会构成一个圆圈,在判断它崇拜的同学的时间戳是否小于等于进入这个圆圈时走过的第一个点的时间戳,如果符合那确实构成了一个圆圈,那就返回圆圈长度(dfn[x]-dfn[a[x]]+1),如果它崇拜的同学的时间戳不满足这个条件,那就说明构成不了一个圆圈,那就返回0;
如果连一开始的判断都不符合(当前x同学崇拜的同学没有被走过),那就继续去当前x同学崇拜的同学搜下去。
代码
#include <iostream>
using namespace std;
const int N=1e5+9;
int mindfn,dfn[N],a[N];//mindfn表示进入一个圆圈前第一个点的时间戳//dfn[i]表示第i个点时间戳
//a[i]表示第i个同学崇拜的同学的编号
int indx;//时间戳,最后时间戳会等于n
int ans=0;
int dfs(int x)
{
dfn[x]=++indx;
if(dfn[a[x]])//如果第x个同学崇拜的同学之前已经走过,那就说明可能会构成一个圆圈
{
//如果如果第x个同学崇拜的同学的时间戳大于等于进入这个元之前第一个点的时间戳,那就说明
//能构成一个圆圈,那就返回一个圆圈的长度
if(dfn[a[x]]>=mindfn)return dfn[x