目录
7-1 输出全排列(C++,DFS)
题目描述:
请编写程序输出前n个正整数的全排列(n<10),并通过9个测试用例(即n从1到9)观察n逐步增大时程序的运行时间。
输入格式:
输入给出正整数n(<10)。
输出格式:
输出1到n的全排列。每种排列占一行,数字间无空格。排列的输出顺序为字典序,即序列 a 1 , a 2 , ⋯ , a n a_1,a_2,⋯,a_n a1,a2,⋯,an排在序列 b 1 b_1 b1, b 2 b_2 b2,⋯, b n b_n bn之前,如果存在k使得 a 1 = b 1 , ⋯ , a k = b k a_1=b_1,⋯,a_k=b_k a1=b1,⋯,ak=bk 并且 a k + 1 < b k + 1 a_k+1<b_k+1 ak+1<bk+1。
输入样例:
3
输出样例:
123
132
213
231
312
321
解题思路:
注意题目要求从小到大输出n位数字的全排列,那么我们尝试的时候就要按1~9的顺序
全排列问题采用DFS即可
以下是实现代码
#include <iostream>
using namespace std;
int num_array[10] = { 0 };
bool book[10] = { 0 };
int n;
void dfs(int now) {//now代表当前是左数第几位,n为总位数
//递归停止条件
if (now == n + 1) {
for (int i = 1; i <= n; i++) {
putchar('0' + num_array[i]);
}
putchar('\n');
return;
}
//递归主体
for (int i = 1; i <= n; i++) {//尝试1~n
if (!(book[i])) {
num_array[now] = i;
book[i] = true;
dfs(now + 1);
book[i] = false;
}
}
}
int main() {
cin >> n;
dfs(1);
return 0;
}
如果有不明白DFS是如何实现全排列的可以接着阅读
找到n位数的全排列本质就是枚举从[ 1 0 n , 1 0 n + 1 − 1 10^n, 10^{n+1}-1 10n,10n+1−1]的所有数字
可以用n层的for循环来实现,但是for循环的层数是需要随着输入的变化而变化的,故可以采用递归实现
void dfs(int now) {
//递归停止条件
if (now == n + 1) {
;//判断是否是全排列
;//输出找到的全排列
return;
}
//递归主体
for (int i = 1; i <= n; i++) {//枚举1~n
dfs(i);
}
}
可以看到,这个算法的时间复杂度是o( n n n^n nn),那么我们需要进行优化
先说明一个简单的情形:如果第一个数字是1,当第二个数字也是1的时候,就可以停止尝试,也就是常说的剪枝
根据这个思路进行优化
bool book[10] = { false };//标记出现过的数字
void dfs(int now) {
//递归停止条件
if (now == n + 1) {
//一定是一种全排列,不需要再判断
;//输出找到的全排列
return;
}
//递归主体
for (int i = 1; i <= n; i++) {//枚举1~n
if (!(book[i])) {//如果i没出现过
book[i] = true;//标记i
dfs(i);
book[i] = false;//不要忘记取消标记
}
}
}
至此,对DFS如何实现全排列的解释完成
7-2 山(C++,BFS)
题目描述:
Drizzle 前往山地统计大山的数目,现在收到这片区域的地图,地图中用0(平地)
和1(山峰)
绘制而成,请你帮忙计算其中的大山数目
山总是被平地四面包围着,每一座山只能在水平或垂直方向上连接相邻的山峰而形成。一座山峰四面被平地包围,这个山峰也算一个大山
另外,你可以假设地图的四面都被平地包围着。
要求:
输入:第一行输入M,N分别表示地图的行列,接下来M行每行输入N个数字表示地图
输出:输出一个整数表示大山的数目
示例:
输入:
4 5
1 1 0 0 0
1 1 0 0 0
0 0 1 0 0
0 0 0 1 1
输出:
3
范围:
对于 5% 的数据:M,N ≤ 10
对于 100% 的数据:M,N ≤ 2000
解题思路:
读完题第一反应就是正面求解,也就是去寻找山的边界,然后就会发现这个思路不太可行
那么换一个思路,现在由于全球变暖导致海平面升高,平地变成了海,而山变成了平地,我们是不能在海面上走的,所以我们只能在山上走,那么我们每次都走完一座山的每一个角落,然后去找下一座山
进一步解释一下,我们已经知道的是哪个格子代表山,我们如果还能知道哪些格子已经走过,我们就能确定山的数目了
如果还不明白,可以理解为染色,我们把第一次到达的山染上"1"的颜色,第二次到达的山染上“2”的颜色……然后我们就可以知道有几座山了
实现代码如下
#include <iostream>
#include <queue>
using namespace std;
const int max_m_n = 2000;
struct position {
int m_x;
int m_y;
};
int M, N;
int map[max_m_n][max_m_n] = { 0 };//地图
bool book[max_m_n][max_m_n] = { false };//地图标记
queue<position> bfs_queue;
int step[4][2] = {//按右、下、左、上的顺序尝试
{0, 1},
{1, 0},
{0, -1},
{-1, 0}
};
void bfs(int x, int y) {
//初始化
struct position temp_pos;
temp_pos.m_x = x, temp_pos.m_y = y;
bfs_queue.push(temp_pos);//队列初始化
book[x][y] = true;//标记
//bfs主体
while (!(bfs_queue.empty())) {
for (int i = 0; i < 4; i++) {//尝试四个方向
temp_pos.m_x = bfs_queue.front().m_x + step[i][0];
temp_pos.m_y = bfs_queue.front().m_y + step[i][1];
if (temp_pos.m_x < 0 || temp_pos.m_x >= M || temp_pos.m_y < 0 || temp_pos.m_y >= N)//越界
continue;
if (book[temp_pos.m_x][temp_pos.m_y])//访问过
continue;
if (!(map[temp_pos.m_x][temp_pos.m_y]))//平地
continue;
bfs_queue.push(temp_pos);
book[temp_pos.m_x][temp_pos.m_y] = true;//标记
}
bfs_queue.pop();//队首尝试完毕,出队
}
}
int main() {
int sum = 0;
cin >> M >> N;
for (int i = 0; i < M; i++) {//输入地图
for (int j = 0; j < N; j++) {
cin >> map[i][j];
}
}
//统计山的数目
for (int i = 0; i < M; i++) {
for (int j = 0; j < N; j++) {
if (!(book[i][j]) && map[i][j]) {//未访问 && 是山
bfs(i, j);
sum++;
}
}
}
cout << sum;
return 0;
}
7-3 跳跃(C++,DFS)
题目描述:
Drizzle 被困到一条充满数字的方块路中,假设这条路由一个非负的整数数组m
组成,Drizzle 最开始的位置在下标 start
处,当他位于下标i
位置时可以向前或者向后跳跃m[i]
步数,已知元素值为0
处的位置是出口,且只能通过出口出去,不可能数组越界,请你通过编程计算出Drizzle能否逃出这里。
要求:
输入:第一行输入数组m
的长度n
第二行输入数组元素,空格分割开 第三行输入起始下标start
示例:
输入:
7
4 2 3 0 3 1 2
5
输出:
True
范围:
- 1 ≤ m . l e n g t h ≤ 5 ∗ 1 0 4 1 \leq m.length \leq 5 * 10^4 1≤m.length≤5∗104
- 0 ≤ m [ i ] < m . l e n g t h 0 \leq m[i] < m.length 0≤m[i]<m.length
- 0 ≤ s t a r t < m . l e n g t h 0 \leq start < m.length 0≤start<m.length
解题思路:
题意可以理解为搜索路径,故用DFS来实现
好像很简单,但是我们实现过程中会发现问题:如何递归
考虑一下我们来到一个格子后有几种情况:
(1)已经走过
(2)未走过:未走过又可以分为两种情况:<1>找到出口 <2>未找到
继续思考,我们每来到一个格子需要进行两次判断,那么有如下几种情况
(1)两侧均已经走过
(2)至少有一侧未走过
所有情况都考虑完毕了,现在我们知道dfs结束条件只有两个
(1)两侧均已经走过
(2)找到出口
第一种情况返回0,第二种情况返回1即可
完整的实现代码如下
#include <iostream>
using namespace std;
int n;
int num_array[500] = { 0 };
bool book[500] = { false };//标记,注意本题dfs无需取消标记,可以自行思考为什么
bool dfs(int now) {//now为当前格子的索引
if (num_array[now] == 0)//找到出口
return 1;
if (now + num_array[now] < n && !(book[now + num_array[now]])) {//未越界 && 未走过
book[now + num_array[now]] = true;//标记
if (dfs(now + num_array[now]))
return 1;//找到出口,连续返回,不需要继续尝试
}
if (now - num_array[now] >= 0 && !(book[now - num_array[now]])) {//未越界 && 未走过
book[now - num_array[now]] = true;//标记
if (dfs(now - num_array[now]))
return 1;//找到出口,连续返回,不需要继续尝试
}
return 0;//两侧均走过
}
int main() {
int start;
cin >> n;
for (int i = 0; i < n; i++)//读入数组
cin >> num_array[i];
cin >> start;//读入起始索引
if(dfs(start))
cout << "True";
else
cout << "False";
return 0;
}
7-4 最长光路(C++,DFS)
题目描述:
小明在做丁达尔效应的实验。
他用胶体填满了一个有 N 行, M 列个格子的透明盒子。同时,为了使实验效果更好,他在盒子的某些格子中装入一些双面镜(用“/”和“\”表示)。
当光线找到双面镜的时候,光路会反射,方向会有90度的转换(如下图所示)。
小明在填充胶体的时候产生了失误,使得有些格子光线无法穿过(我们可以认为这些格子把光线都吸收了),这些格子用大写字母“C”来表示。
现在,小明已经制作好了实验装置,他将激光光源放在第 s x s_x sx行的第 s y s_y sy个格子上。激光光源可以朝向上下左右四个方向,分别用URDL四个字母表示(“U”-上, “R”-右, “D”-下, “L”-左)。
为了让实验效果更好,小明希望光的光路越酷越好。
对小明来说,最酷的光路就是包含环的光路,这种情况下,光不会射向盒子外面,而是一直在盒子内循环(数据保证光源处的点是光线可以通过,即起点一点是".")。
如果光源朝各个方向摆设,最终光线都会射向盒子外部,那么小明认为经过格子最多的光路是最酷的。
现在小明想知道光源朝哪个方向放置光路最酷。
如果光路是不包含环的,那么他还想知道光路经过的格子数目是多少。
注意
如果有多个方向是最优解,那么我们按照“U”-上, “R”-右, “D”-下, “L”-左的优先级来选择最终的答案,即如果向左和向右都是最优解,我们选择向右的方案。
同时,温馨提醒,c++中“\”符号可以用'\\'来表示
输入格式:
第一行两个数 N,M(1≤N,M≤500) 表示有N 行 M 列的格子。
接下来 N 行, 每行 M 个字符,表示盒子的具体情况。 “/”和“\”表示装有不同朝向的镜子的格子。“C”表示光线无法通过的格子。“.”表示正常且没有镜子的格子。
最后输入两个数字
s
x
,
s
y
s_x,s_y
sx,sy表示光源所在的点。
输出格式:
第一行输出一个大写字母,表示最酷的光路应该超哪个方向摆放。
第二行输出光路经过的格子数。如果光路中包含环,则换成输出字符串“COOL”。
输入样例1:
5 5
../.\
.....
.C...
...C.
\.../
3 3
输出样例1:
U
17
输入样例2:
5 7
/.....\
../..\.
\...../
/.....\
\.\.../
3 3
输出样例2
R
COOL
样例解释
S为起点
../.\
.....
.CS..
...C.
\.../
'U' 方向
*.***
*.*.*
*C*.*
*..C*
*****
17个格子
'R'方向
../.\
.....
.C***
...C.
\.../
3个格子
'D'方向
../.\
.....
.C*..
..*C.
\.*./
3个格子
'L'方向
../.\
.....
.C*..
...C.
\.../
1个格子
解题思路:
(1)光的传播方向:
我们把光定义出四种状态:向上/右/下/左传播
地图上的双面镜有两种状态:‘\\‘和’/’
我们可以根据这两种状态对光的传播方向进行改变
(2)闭环判断:
充分必要条件是再次回到起点,且传播方向与初始状态相同
(3)DFS终止条件:
<1>越界
<2>闭环
<3>被吸收(‘C’)
此外,由于DFS无返回后操作,可以用while循环实现
实现DFS的大致思路:光传播一格,然后进行判断
代码实现如下
#include <iostream>
using namespace std;
int toward[4][2] = {//上、右、下、左
{-1, 0},
{0, 1},
{1, 0},
{0, -1}
};
string map[500];
int main() {
int N, M, start_x, start_y, max_sum = 0, max_light = 0;
cin >> N >> M;
for (int i = 0; i < N; i++)
cin >> map[i];
cin >> start_x >> start_y;
for (int i = 0; i < 4; i++) {//尝试四个方向
int light = i, cur_x = start_x - 1, cur_y = start_y - 1, sum = 1;//注意:修改起始下标,并且起点也算格子
while (true) {
cur_x += toward[light][0];
cur_y += toward[light][1];
sum++;
if (cur_x == start_x - 1 && cur_y == start_y - 1 && light == i) {//COOL!
switch (i)
{
case 0:
putchar('U');
break;
case 1:
putchar('R');
break;
case 2:
putchar('D');
break;
case 3:
putchar('L');
break;
default:
break;
}
putchar('\n');
cout << "COOL";
return 0;
}
if (cur_x < 0 || cur_x >= N || cur_y < 0 || cur_y >= M) {//出界
sum--;
break;
}
else if (map[cur_x][cur_y] == '.')//正常传播
continue;
else if (map[cur_x][cur_y] == 'C') {//吸收
sum--;
break;
}
else if (map[cur_x][cur_y] == '\\') {
switch (light)//改变传播方向
{
case 0://光向上传播
light = 3;//转向左
break;
case 1://光向右传播
light = 2;//转向下
break;
case 2://光向下传播
light = 1;//转向右
break;
case 3://光向左传播
light = 0;//转向上
break;
default:
break;
}
}
else if (map[cur_x][cur_y] == '/') {
switch (light)//改变传播方向
{
case 0://光向上传播
light = 1;//转向右
break;
case 1://光向右传播
light = 0;//转向上
break;
case 2://光向下传播
light = 3;//转向左
break;
case 3://光向左传播
light = 2;//转向下
break;
default:
break;
}
}
}//尝试结束
if (max_sum < sum) {
max_sum = sum;
max_light = i;
}
}
switch (max_light)
{
case 0:
putchar('U');
break;
case 1:
putchar('R');
break;
case 2:
putchar('D');
break;
case 3:
putchar('L');
break;
default:
break;
}
putchar('\n');
cout << max_sum;
return 0;
}
7-5 回文数文回
题目描述:
我们称一个数是回文的,当且仅当它正着读和倒着读是相同的。
例如11或11455411是回文的,而10或1919810不是回文的。
现在给定一个数n,你需要求出区间[ 1 0 8 10^8 108,n]中所有的回文数。
输入格式:
一行一个整数n( 1 0 8 ≤ n < 1 0 9 10^8≤n<10^9 108≤n<109)
输出格式:
输出一行一个数,表示题目所求区间中回文数的数量。
输入样例:
在这里给出一组输入。例如:
100000001
输出样例:
在这里给出相应的输出。例如:
1
解题思路:
题中给出了回文数的固定长度为9,由于回文数是对称的,我们只需要考虑前5个符号的序列即可
将前5个符号看做一个整数,每一个整数都对应着一个序列
如果输入的n是2345671991的话,在前五个数=23456之前,不可能有大于n的整数
那么我们只需要判断234565432是否符合题意即可
如果我们采用如下的方式
while(left < right) {
if (str[left] > str[right]) {
sum--;
break;
}
left++, right--;//从两侧向中间
}
很快就会发现问题,按这个判断算法,234565432是不符合题意的
那么我们修改一下
while(left >= 0 && right <= 8) {
if (str[left] > str[right]) {
sum--;
break;
}
left--, right++;//从中间向两侧
}
显然,234565432仍然不符合题意,因为4 > 1
我们会发现,不能单独看每一个符号,因为它们是一个整数
所以正确的判断算法如下
reverse(&str[0], &str[0] + 4);
stoi(str.substr(0, 4)) > stoi(str.substr(5, 9)) ? int_n-- : int_n;
注意只能反转前4位,可以想一想为什么反转前5位会出现问题
本题完整的实现代码如下
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
int main() {
string n;
int int_n;
cin >> n;
int_n = stoi(n.substr(0, 5));
reverse(&n[0], &n[0] + 4);
stoi(n.substr(0, 4)) > stoi(n.substr(5, 9)) ? int_n-- : int_n;
cout << int_n - 1e4 + 1;
return 0;
}