文章目录
DFS(深度优先搜索)算法
是一种在图或树等数据结构中搜索的常见算法。
思想:
它从一个起始节点开始,沿着一条路径尽可能深入地搜索,直到无法继续或达到目标,然后回溯并探索其他路径。(不撞南墙不回头)。
- DFS 适用于路径问题、回溯问题、生成问题等。
- BFS 适用于最短路径问题、层次遍历问题等。
关键:
- 初始状态设置
- 递归逻辑:正确实现递归,和对每个节点的处理
- 回溯机制
- 状态标记:标记已访问的,避免重复,同时要注意,递归后的操作(有时不能 =1/=0,需要+1,-1.下面例题就是这样)
- 边界条件:有时需要if判断,数组定义时从1~n
- 数据结构:
数组:
- 优势:访问速度快,可通过索引直接访问元素。
- 适用场景:需要高效随机访问的数据。
链表:
- 优势:插入和删除操作相对简单。
- 适用场景:经常进行插入和删除操作的情况。
栈stack(先进后出):
- 优势:遵循后进先出原则,支持回溯操作。
- 适用场景:深度优先搜索等需要回溯的问题。
哈希表(hash table):
- 优势:查询速度快,可快速判断元素是否存在。
- 适用场景:需要快速查找和判断的情况。
树或图:
- 优势:自然地表示层次结构或关系。
- 适用场景:处理具有层次或关系的数据。
集合(set):
- 优势:简单地表示元素的集合,不允许重复元素。
- 适用场景:需要存储不重复元素的情况。
模板:
void dfs(Node node)
{
if (某些条件满足) {
// 处理当前节点
}
for (每个与当前节点相连的节点) {
dfs(连接的节点);
}
}
求细胞数量
题目描述
一矩形阵列由数字
0
0
0 到
9
9
9 组成,数字
1
1
1 到
9
9
9 代表细胞,细胞的定义为沿细胞数字上下左右若还是细胞数字则为同一细胞,求给定矩形阵列的细胞个数。
输入格式
第一行两个整数代表矩阵大小 n n n 和 m m m。
接下来
n
n
n 行,每行一个长度为
m
m
m 的只含字符 0
到 9
的字符串,代表这个
n
×
m
n \times m
n×m 的矩阵。
输出格式
一行一个整数代表细胞个数。
代码
#include<bits/stdc++.h>
using namespace std;
int p[4][2]={{0,1},{-1,0},{0,-1},{1,0}};
int m,n,sum=0;
bool b[105][105];
char a[102][102];
void dfs(int x,int y)
{
b[x][y]=1;
for(int i=0;i<4;i++)
if(x+p[i][0]>=0&&x+p[i][0]<n&&y+p[i][1]>=0&&y+p[i][1]<m&&a[x+p[i][0]][y+p[i][1]]!='0'&&b[x+p[i][0]][y+p[i][1]]==0)
//范围一定别忘了限定
{ dfs(x+p[i][0],y+p[i][1]);//在范围内,未标记,又符合,就递归,直至不满足,退出
}
}
int main()
{
cin>>n>>m;
for(int i=0;i<n;i++)
for(int j=0;j<m;j++)
cin>>a[i][j];
for(int i=0;i<n;i++)
for(int j=0;j<m;j++)
if(a[i][j]!='0'&&b[i][j]==0)//别忘加引号,这是字符
{
sum++;dfs(i,j);//sum++,在函数里找并标记一个完整细胞
}
cout<<sum;
}
这个是比较简单的,连通体类型,同类型P1596 水坑,可以练习
[USACO1.5] 八皇后 Checker Challenge
题目描述
一个如下的 6 × 6 6 \times 6 6×6 的跳棋棋盘,有六个棋子被放置在棋盘上,使得每行、每列有且只有一个,每条对角线(包括两条主对角线的所有平行线)上至多有一个棋子。
上面的布局可以用序列 2 4 6 1 3 5 2\ 4\ 6\ 1\ 3\ 5 2 4 6 1 3 5 来描述,第 i i i 个数字表示在第 i i i 行的相应位置有一个棋子,如下:
行号 1 2 3 4 5 6 1\ 2\ 3\ 4\ 5\ 6 1 2 3 4 5 6
列号 2 4 6 1 3 5 2\ 4\ 6\ 1\ 3\ 5 2 4 6 1 3 5
这只是棋子放置的一个解。请编一个程序找出所有棋子放置的解。
并把它们以上面的序列方法输出,解按字典顺序排列。
请输出前
3
3
3 个解。最后一行是解的总个数。
输入格式
一行一个正整数 n n n,表示棋盘是 n × n n \times n n×n 大小的。
输出格式
前三行为前三个解,每个解的两个数字之间用一个空格隔开。第四行只有一个数字,表示解的总数。
代码
#include<bits/stdc++.h>
using namespace std;
int a[100],b[100],c[100],d[100],n,s;
//每行、每列有且只有一个,每条对角线上至多有一个棋子,开四个数组来标记
void print()
{
s++;//加上一种结果
if(s<=3)
{
for(int i=1;i<=n;i++)
cout<<a[i]<<" ";
cout<<endl;
}
}
int search(int i)
{
for(int j=1;j<=n;j++)
{
if(b[j]==0&&c[i+j]==0&&d[i-j+n]==0)
{
a[i]=j,b[j]=1,c[i+j]=1,d[i-j+n]=1;
//a来控制行,存储的位置也是最终结果,b控制列,c控制其中一条对角线(行列相加相等),d另一条对角线(行列相减相等+n,保证数据范围)
if(i==n) //标记到最后一行
print();//调用输出的函数
else
search(i+1);//下一行找
b[j]=0,c[i+j]=0,d[i-j+n]=0;//没找到,返回时,取消标记
}
}
return 0;
}
int main()
{
cin>>n;
search(1);
cout<<s<<endl;
return 0;
}
这也是一个经典的例题,八皇后类型,关于对角线的约束条件,可以注意一下
[USACO06FEB] Backward Digit Sums G/S
题面翻译
有这么一个游戏:
写出一个 1 ∼ n 1\sim n 1∼n 的排列 a a a,然后每次将相邻两个数相加,构成新的序列,再对新序列进行这样的操作,显然每次构成的序列都比上一次的序列长度少 1 1 1,直到只剩下一个数字位置。
下面是一个例子:
- 3 , 1 , 2 , 4 3,1,2,4 3,1,2,4;
- 4 , 3 , 6 4,3,6 4,3,6;
- 7 , 9 7,9 7,9;
- 16 16 16。
最后得到 16 16 16 这样一个数字。
现在想要倒着玩这样一个游戏,如果知道 n n n,以及最后得到的数字的大小 s u m sum sum,请你求出最初序列 a i a_i ai(应该是一个 1 ∼ n 1\sim n 1∼n 的排列)。若答案有多种可能,则输出字典序最小的那一个。
我们称序列
a
=
⟨
a
1
,
a
2
,
⋯
,
a
n
⟩
a=\lang a_1,a_2,\cdots,a_n\rang
a=⟨a1,a2,⋯,an⟩ 的字典序小于序列
b
=
⟨
b
1
,
b
2
,
⋯
,
b
n
⟩
b=\lang b_1,b_2,\cdots,b_n\rang
b=⟨b1,b2,⋯,bn⟩ 的字典序,当且仅当存在一个位置
p
p
p,满足
a
1
=
b
1
,
a
2
=
b
2
,
⋯
,
a
p
−
1
=
b
p
−
1
,
a
p
<
b
p
a_1=b_1,a_2=b_2,\cdots,a_{p-1}=b_{p-1},a_p<b_p
a1=b1,a2=b2,⋯,ap−1=bp−1,ap<bp。
输入格式
共一行两个正整数
n
,
s
u
m
n,sum
n,sum。
输出格式
输出包括一行,为字典序最小的那个答案。
当无解的时候,请什么也不输出。
代码
#include<bits/stdc++.h>
using namespace std;
int n,p;
int a[13],c[13][13];
bool b[13];
void dfs(int dep ,int s)
{
if(s>p)
return ;
if(dep>n)//选够n个数了
{
if(s==p)//结果=p
{
for(int i=1;i<=n;i++)
cout<<a[i]<<" ";
exit(0); //表示整个程序的结束
}
return ;
}
for(int i=1;i<=n;i++)//i表示一行中数字,从1开始 。正好也符合,最小字典序
{
if(b[i]==false)//没选过i这个数字
{
b[i]=true;//答案数字不同,所以要标记一下
a[dep]=i;//把数字存入a数组
dfs(dep+1,s+i*c[n][dep]);//系数是递归三角,把答案从1循环开始试
b[i]=false;
}
}
}
int main()
{
cin>>n>>p;//初始为n,也是进行n次。答案是n个数
//下面是关于杨辉三角排列
c[1][1]=1;//第一行
for(int i=2;i<=n;i++) //i表示行
for(int j=1;j<=i;j++)//一行一个数字,第n行,n个,j表示列
c[i][j]=c[i-1][j]+c[i-1][j-1];
dfs(1,0);//从第一个数开始,
return 0;
}
这个题目涉及到杨辉三角的规律,
由图可知,最后的结果是有关初始项多项式的答案,且系数和杨辉三角有关
奇怪的电梯
题目描述
呵呵,有一天我做了一个梦,梦见了一种很奇怪的电梯。大楼的每一层楼都可以停电梯,而且第 i i i 层楼( 1 ≤ i ≤ N 1 \le i \le N 1≤i≤N)上有一个数字 K i K_i Ki( 0 ≤ K i ≤ N 0 \le K_i \le N 0≤Ki≤N)。电梯只有四个按钮:开,关,上,下。上下的层数等于当前楼层上的那个数字。当然,如果不能满足要求,相应的按钮就会失灵。例如: 3 , 3 , 1 , 2 , 5 3, 3, 1, 2, 5 3,3,1,2,5 代表了 K i K_i Ki( K 1 = 3 K_1=3 K1=3, K 2 = 3 K_2=3 K2=3,……),从 1 1 1 楼开始。在 1 1 1 楼,按“上”可以到 4 4 4 楼,按“下”是不起作用的,因为没有 − 2 -2 −2 楼。那么,从 A A A 楼到 B B B 楼至少要按几次按钮呢?
输入格式
共二行。
第一行为三个用空格隔开的正整数,表示 N , A , B N, A, B N,A,B( 1 ≤ N ≤ 200 1 \le N \le 200 1≤N≤200, 1 ≤ A , B ≤ N 1 \le A, B \le N 1≤A,B≤N)。
第二行为 N N N 个用空格隔开的非负整数,表示 K i K_i Ki。
输出格式
一行,即最少按键次数,若无法到达,则输出 -1
。
#include<bits/stdc++.h>
using namespace std;//再开一个数组,用来更新比较,找到最小sum
int a[201],A,B,N,m[201];//需要把sum放入函数调用中,不然它会连续改变
void dfs(int h,int sum)//如果用dfs,要不断更新,判断
{
m[h]=sum;
if(h-a[h]<=N&&sum+1<m[h-a[h]] )//最高N
dfs(h-a[h],sum+1);
if(a[h]+h>0&&sum+1<m[a[h]+h] ) //最低1
dfs(a[h]+h,sum+1);
return;
}
int main()
{
memset(m,0x3f,sizeof(m));//初始化为最大
cin>>N>>A>>B;
for(int i=1;i<=N;i++)
cin>>a[i];
dfs(A ,0);//当找不到时,m[B]对应的值极大
if(m[B]==0x3f3f3f3f)
cout<<-1;
else cout<<m[B];
}
取数游戏
题目描述
一个 N × M N\times M N×M 的由非负整数构成的数字矩阵,你需要在其中取出若干个数字,使得取出的任意两个数字不相邻(若一个数字在另外一个数字相邻 8 8 8 个格子中的一个即认为这两个数字相邻),求取出数字和最大是多少。
输入格式
第一行有一个正整数 T T T,表示了有 T T T 组数据。
对于每一组数据,第一行有两个正整数 N N N 和 M M M,表示了数字矩阵为 N N N 行 M M M 列。
接下来 N N N 行,每行 M M M 个非负整数,描述了这个数字矩阵。
输出格式
共 T T T 行,每行一个非负整数,输出所求得的答案。
代码
#include<bits/stdc++.h>
using namespace std;
int p[8][2]={{0,1},{1,0},{-1,0},{0,-1},{1,1},{-1,1},{1,-1},{-1,-1}};
int a[8][8],n,m,ma;
int b[8][8];
void dfs(int x,int y,int z)
{
if(y>m)//这个题用换行,y+1,x+1实现
{
dfs(x+1,0,z);
return ;
}
if(x>n)
{
ma=max(ma,z);
return ;
}
dfs(x,y+1,z);
if(b[x][y]==0)
{
for(int i=0;i<8;i++)
++b[x+p[i][0]][y+p[i][1]];//这里是++,不能是=1,这样可能在某个递归中
dfs(x,y+1,z+a[x][y]);//又被改成零了,因为可能有多重标记,递归中直接改成零了
for(int i=0;i<8;i++)
--b[x+p[i][0]][y+p[i][1]];
}
}
int main()
{
int t;
cin>>t;
while(t--)
{
memset(b,0,sizeof(b));
cin>>n>>m;
ma=0;
for(int i=1;i<=n;i++)//这里要从1开始到n,搜索边界很重要,在
for(int j=1;j<=m;j++)//这个题里正好不用判断因为一直y+1.
cin>>a[i][j];
dfs(1,1,0);
cout<<ma<<endl;
}
return 0;
}
高手去散步
题目描述
鳌头山上有
n
n
n 个观景点,观景点两两之间有游步道共
m
m
m 条。高手的那个它,不喜欢太刺激的过程,因此那些没有路的观景点高手是不会选择去的。另外,她也不喜欢去同一个观景点一次以上。而高手想让他们在一起的路程最长(观景时它不会理高手),已知高手的穿梭机可以让他们在任意一个观景点出发,也在任意一个观景点结束。
输入格式
第一行,两个用空格隔开的整数 n n n 、 m . m. m. 之后 m m m 行,为每条游步道的信息:两端观景点编号、长度。
输出格式
一个整数,表示他们最长相伴的路程。
代码
#include<bits/stdc++.h>
using namespace std;
int n,m,a[55][55],mark[55],ans,ma;//这里用邻接矩阵去存放路线长度
void dfs(int x,int sum)
{
ma=max(sum,ma);//从i开始的最大长度
for(int i=1;i<=n;i++)
if(!mark[i]&&a[i][x]!=0)
{
mark[i]=1;
dfs(i,sum+a[i][x]);
mark[i]=0;
}
}
int main()
{
int b,c,d;
cin>>n>>m;
while(m--)
{
cin>>b>>c>>d;
a[b][c]=d,a[c][b]=d;//此题b-c,c-b是双向
}
for(int i=1;i<=n;i++)
{
mark[i]=1;
dfs(i,0);
ans=max(ans,ma);//ma是从某点开始的最大长度
memset(mark,0,sizeof(mark));
}
cout<<ans;
}
还有一道P1700 [USACO19OPEN] Milk Factory B
可以看一下