第二次双周赛
第一题
题目1
输出全排列
思路1
使用dfs
代码1
#include<iostream>
using namespace std;
int n;
//是否访问过
bool visited[10];
//s为这个排列的字符串
void dfs(int c, string s)
{
//全部访问过了
if (c == n)
{
//输出
cout << s << endl;
}
//遍历
for (int i = 1; i <= n; i++)
{
if (!visited[i])
{
//标记、进行下一次dfs、取消标记
visited[i] = true;
dfs(c + 1, s + (char)(i + '0'));
visited[i] = false;
}
}
}
int main()
{
cin >> n;
//对每个数进行一次dfs,得到这个数开头的排列
for (int i = 1; i <= n; i++)
{
visited[i] = true;
//初始字符串
string s = "";
dfs(1, s + (char)(i + '0'));
visited[i] = false;
}
return 0;
}
第二题
题目2
给一张地图,有部分为山峰(1),有部分为平地(0),求山的个数(上下左右连在一起的山峰)
思路2
对每个点用dfs(不需要取消标记)或者bfs,将这个点所在的山内所有1都标记为访问过
代码2
#include<iostream>
#include<utility>
#include<queue>
using namespace std;
//m、n、山个数
int m, n, cnt = 0;
//标记
bool visited[2001][2001];
//地图
bool mp[2001][2001];
//四个方向对应在x和y上的偏移量
int xoffset[] = {0, 1, 0, -1}, yoffset[] = {1, 0, -1, 0};
//bfs的队列
queue<pair<int, int> > q;
int main()
{
cin >> m >> n;
for (int i = 1; i <= m; i++)
{
for (int j = 1; j <= n; j++)
{
cin >> mp[i][j];
}
}
//遍历每个点
for (int i = 1; i <= m; i++)
{
for (int j = 1; j <= n; j++)
{
//当这个点没有被标记为别的山的一部分并且这个点是山峰,则进行bfs
if (!visited[i][j] && mp[i][j])
{
//进行一次bfs就代表找到一个山
cnt++;
//入队列准备搜索
q.push(make_pair(i, j));
//标记
visited[i][j] = true;
//bfs
while (!q.empty())
{
//当前点坐标
int x = q.front().first, y = q.front().second;
//出队列
q.pop();
//在四个方向上找下一个点
for (int k = 0; k < 4; k++)
{
int nextx = x + xoffset[k], nexty = y + yoffset[k];
//在图内并且是山峰并且还没访问过
if (nextx >= 1 && nexty >= 1 && nextx <= m && nexty <= n && mp[nextx][nexty] && !visited[nextx][nexty])
{
//入队列
q.push(make_pair(nextx, nexty));
//标记
visited[nextx][nexty] = true;
}
}
}
}
}
}
cout << cnt << endl;
return 0;
}
第三题
题目3
有一条充满数字的方块路由一个非负的整数数组m组成,最开始的位置在下标start,当位于下标i位置时可以向前或者向后跳跃m[i]步数,已知元素值为0处的位置是出口,问是否可以到达出口
思路3
用dfs或bfs从起点按规则搜索,如果到得了0就能到达
代码3
#include<iostream>
#include<queue>
using namespace std;
//n和起点坐标
int n, start;
//bfs是否访问过
bool visited[50001];
//数组m
int mp[50001];
//bfs队列
queue<int> q;
int main()
{
cin >> n;
for (int i = 0; i < n; i++)
{
cin >> mp[i];
}
cin >> start;
//将起点标记并入队列
visited[start] = true;
q.push(start);
//指示是否可行
bool isPossible = false;
//bfs
while (!q.empty())
{
//当前点
int p = q.front();
//出队列
q.pop();
//到出口了
if (mp[p] == 0)
{
//输出
cout << "True" << endl;
//更新标记
isPossible = true;
//提前终止bfs
break;
}
//向后的点位置、向前的点位置
int next1 = p + mp[p], next2 = p - mp[p];
//在路上并且没访问过
if (next1 >= 0 && next1 < n && !visited[next1])
{
//入队列并标记
q.push(next1);
visited[next1] = true;
}
//在路上并且没访问过
if (next2 >= 0 && next2 < n&& !visited[next2])
{
//入队列并标记
q.push(next2);
visited[next2] = true;
}
}
//如果标记没有被更新,则到不了出口
if (!isPossible)
{
cout << "False" << endl;
}
return 0;
}
第四题
题目4
给一个数n,求出区间[1e8, n]中所有的回文数
思路4
数据非常大,直接暴力遍历肯定超时。
可以考虑生成所有的回文数,再计数。回文数的个数较少,更容易完成。
由于题目保证了数字为9为,则可以先选定中间数字,再向两边添加相同的数字4次,就可以生成一个回文数。
放在搜索的考试里,肯定是用搜索。用dfs得到所有的情况。
代码4
#include<iostream>
using namespace std;
//回文数每一位的数字
int b[9];
//n、回文数个数
int n, cnt = 0;
//判断生成的9位回文数是否在给定范围内
bool isInRange()
{
//将b数组中的每一位组合成一个数
int t = 0;
for (int i = 0; i < 9; i++)
{
t *= 10;
t += b[i];
}
//判断
if (t >= 1e8 && t <= n)
{
return true;
}
else
{
return false;
}
}
//step为生成次数
void dfs(int step)
{
//如果是第五次(已经生成了4次),就生成结束
if (step == 5)
{
//如果在范围内,就可以计数
if (isInRange())
{
cnt++;
}
return;
}
//添加第1、2、3次和第4次不同,第1、2、3次可以使用任何数字而第4次不能用0
//start是遍历所有的数字的起点
int start;
//第4次就从1开始
if (step == 4)
{
start = 1;
}
//其他时候就从0开始
else
{
start = 0;
}
for (int i = start; i <= 9; i++)
{
//填数字
b[4 - step] = b[4 + step] = i; // 0 1 2 3 4 5 6 7 8
//下一次搜索
dfs(step + 1);
}
}
int main()
{
cin >> n;
//填中间数字和开始搜索
for (int i = 0; i <= 9; i++)
{
b[4] = i;
dfs(1);
}
cout << cnt << endl;
return 0;
}
第五题
题目5
有一个盒子,里面有n*m个格子。有些格子里是平面镜,根据平面镜的方向会改变光线的方向。有些格子会吸收光线。其他格子对光线没有影响。现在一个地方有光源,向某个方向发射光线。小明调整光源的方向,想得到最酷的光路(环形光路最酷,其他情况越长越酷)
思路5
用dfs模拟光线的行进,直到出现了循环或者光线被吸收或光线离开盒子。
出现循环的判断方法:光线从一个方向射入了一个格子两次。
为了处理循环的特殊情况,只用二维的标记数字是不够的(从不同方向进入同一个格子不算循环),所以给标记数组加一维记录方向。
代码5
#include<iostream>
#include<string.h>
using namespace std;
//方向的定义:0-U, 1-R, 2-D, 3-L,按照题给的优先级顺序
//n、m、起始点坐标、起始光源方向
int n, m, sx, sy, startdirection;
//地图情况
char mp[501][501];
//标记数组,最后一位记录方向
bool visited[501][501][4];
//光线在某个方向上前进一个对应的x和y方向的位移
int directionx[] = {-1, 0, 1, 0}, directiony[] = {0, 1, 0, -1};
//四个方向的长度
int ans[4];
//是否出现了循环
bool isCool = false;
//如果出现了循环,出现循环的起始方向
int cooldirection;
//每个方向对应的字符表示
char directionchar[] = {'U', 'R', 'D', 'L'};
//处理平面镜反射光线的情况,根据mirror,返回direction方向的光线经过反射的方向
int changeDirection(char mirror, int direction)
{
if (mirror == '\\')
{
switch (direction)
{
case 0:
return 3;
case 1:
return 2;
case 2:
return 1;
case 3:
return 0;
}
}
else if (mirror == '/')
{
switch (direction)
{
case 0:
return 1;
case 1:
return 0;
case 2:
return 3;
case 3:
return 2;
}
}
return -1;
}
//dfs 参数:当前坐标x和y、当前方向、当前经过了格子数
void dfs(int x, int y, int direction, int step)
{
//如果光线离开盒子或者被吸收了,就可以记录该方向的长度并结束dfs
if (x <= 0 || y <= 0 || x > n || y > m || mp[x][y] == 'C')
{
ans[startdirection] = step;
return;
}
//从一个方向经过一个格子两次-循环。记录相关的数据,并结束dfs
if (visited[x][y][direction])
{
isCool = true;
cooldirection = startdirection;
return;
}
//是镜子,则用刚刚的函数处理方向
if (mp[x][y] == '\\' || mp[x][y] == '/')
{
visited[x][y][direction] = true;
direction = changeDirection(mp[x][y], direction);
}
//什么都没有
if (mp[x][y] == '.')
{
visited[x][y][direction] = true;
}
//下一次dfs
dfs(x + directionx[direction], y + directiony[direction], direction, step + 1);
}
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <=m; j++)
{
cin >> mp[i][j];
}
}
cin >> sx >> sy;
//循环四次方向,对每个方向都dfs一次
for (int i = 0; i < 4; i++)
{
//清楚标记数据
memset(visited, 0, sizeof(visited));
//设置起始方向
startdirection = i;
/dfs
dfs(sx, sy, startdirection, 0);
//如果循环了,就不需要再考虑剩下的情况了,因为方向顺序按照题给的优先级,剩下的情况优先级更低
if (isCool)
{
break;
}
}
//有循环的情况
if (isCool)
{
cout << directionchar[cooldirection] << endl;
cout << "COOL" << endl;
}
//都是有限格数的情况
else
{
//求最大值和最大值对应的方向
int max = ans[0], maxi = 0;
for (int i = 1; i < 4; i++)
{
if (ans[i] > max)
{
max = ans[i];
maxi = i;
}
}
cout << directionchar[maxi] << endl;
cout << max << endl;
}
return 0;
}