*DFS(深度优先搜索)算法*
是一种在图或树等数据结构中搜索的常见算法。
思想:
它从一个起始节点开始,沿着一条路径尽可能深入地搜索,直到无法继续或达到目标,然后回溯并探索其他路径。(不撞南墙不回头)。
关键:
- 初始状态设置
- 递归逻辑:正确实现递归,和对每个节点的处理
- 回溯机制
- 状态标记:标记已访问的,避免重复,同时要注意,递归后的操作(有时不能 =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 的矩阵。
输出格式
一行一个整数代表细胞个数。
提示
数据规模与约定
对于 100 % 100\% 100% 的数据,保证 1 ≤ n , m ≤ 100 1 \le n,m \le 100 1≤n,m≤100。
代码
#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 大小的。
输出格式
前三行为前三个解,每个解的两个数字之间用一个空格隔开。第四行只有一个数字,表示解的总数。
提示
【数据范围】
对于
100
%
100\%
100% 的数据,
6
≤
n
≤
13
6 \le n \le 13
6≤n≤13。
代码
#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。
输出格式
输出包括一行,为字典序最小的那个答案。
当无解的时候,请什么也不输出。
提示
- 对于 40 % 40\% 40% 的数据, 1 ≤ n ≤ 7 1\le n\le 7 1≤n≤7;
- 对于 80 % 80\% 80% 的数据, 1 ≤ n ≤ 10 1\le n \le 10 1≤n≤10;
- 对于 100 % 100\% 100% 的数据, 1 ≤ n ≤ 12 1\le n \le 12 1≤n≤12, 1 ≤ s u m ≤ 12345 1\le sum\le 12345 1≤sum≤12345。
代码
#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;
}
这个题目涉及到杨辉三角的规律,
由图可知,最后的结果是有关初始项多项式的答案,且系数和杨辉三角有关
奇怪的电梯
题目背景
感谢 @yummy 提供的一些数据。
题目描述
呵呵,有一天我做了一个梦,梦见了一种很奇怪的电梯。大楼的每一层楼都可以停电梯,而且第 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
。
提示
对于 100 % 100 \% 100% 的数据, 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, 0 ≤ K i ≤ N 0 \le K_i \le N 0≤Ki≤N。
本题共 16 16 16 个测试点,前 15 15 15 个每个测试点 6 6 6 分,最后一个测试点 10 10 10 分。
#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 行,每行一个非负整数,输出所求得的答案。
样例 #1
样例输入 #1
3
4 4
67 75 63 10
29 29 92 14
21 68 71 56
8 67 91 25
2 3
87 70 85
10 3 17
3 3
1 1 1
1 99 1
1 1 1
样例输出 #1
271
172
99
提示
样例解释
对于第一组数据,取数方式如下:
[ 67 ] 75 63 10 29 29 [ 92 ] 14 [ 21 ] 68 71 56 8 67 [ 91 ] 25 \begin{matrix} [67] & 75 & 63 & 10 \\ 29 & 29 & [92] & 14 \\ [21] & 68 & 71 & 56 \\ 8 & 67 & [91] & 25 \\ \end{matrix} [67]29[21]87529686763[92]71[91]10145625
数据范围及约定
- 对于 20 % 20\% 20%的数据, 1 ≤ N , M ≤ 3 1\le N, M \le 3 1≤N,M≤3;
- 对于 40 % 40\% 40%的数据, 1 ≤ N , M ≤ 4 1\le N, M\le 4 1≤N,M≤4;
- 对于 60 % 60\% 60%的数据, 1 ≤ N , M ≤ 5 1\le N, M\le 5 1≤N,M≤5;
- 对于 100 % 100\% 100%的数据, 1 ≤ N , M ≤ 6 1\le N, M\le 6 1≤N,M≤6, 1 ≤ T ≤ 20 1\le T\le 20 1≤T≤20。
代码
#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 行,为每条游步道的信息:两端观景点编号、长度。
输出格式
一个整数,表示他们最长相伴的路程。
样例 #1
样例输入 #1
4 6
1 2 10
2 3 20
3 4 30
4 1 40
1 3 50
2 4 60
样例输出 #1
150
提示
对于 100 % 100\% 100% 的数据: n ≤ 20 n \le 20 n≤20, m ≤ 50 m \le 50 m≤50,保证观景点两两之间不会有多条游步道连接。
#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
可以看一下