NEUQACM双周赛(二)

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+11]的所有数字

可以用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 1m.length5104
  • 0 ≤ m [ i ] < m . l e n g t h 0 \leq m[i] < m.length 0m[i]<m.length
  • 0 ≤ s t a r t < m . l e n g t h 0 \leq start < m.length 0start<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度的转换(如下图所示)。

reflection.JPG

小明在填充胶体的时候产生了失误,使得有些格子光线无法穿过(我们可以认为这些格子把光线都吸收了),这些格子用大写字母“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) 表示有NM 列的格子。
接下来 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 108n<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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

WitheredSakura_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值