A. 排列
请你构造一个长度为 n n n 的数组 a 1 , a 2 , … , a n a_1,a_2,…,a_n a1,a2,…,an。
要求:
- 该数组是一个 1 ∼ n 1\sim n 1∼n 的排列。
- 对于所有 1 ≤ i ≤ n 1\leq i\leq n 1≤i≤n,满足 a i ≠ i a_i\neq i ai=i。
输入格式
第一行包含整数 T T T,表示共有 T T T 组测试数据。
每组数据占一行,包含一个整数 n n n。
输出格式
每组数据输出一行结果,包含 n n n 个空格隔开的整数 a 1 , a 2 , … , a n a_1,a_2,…,a_n a1,a2,…,an。
如果方案不唯一,输出任意合理方案均可。
保证一定有解。
数据范围
本题共两个测试点。
小测试点,如样例所示。
大测试点满足:
1
≤
T
≤
100
1\leq T\leq 100
1≤T≤100,
2
≤
n
≤
100
2\leq n\leq 100
2≤n≤100。
输入样例:
2
2
5
输出样例:
2 1
2 1 5 3 4
依次后移一位即可,这玩意 WA 了五发,废了废了。
Code
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
int main()
{
int T;
cin >> T;
while (T -- )
{
int n;
cin >> n;
for (int i = 0; i < n; i ++ )
cout << (i + 1) % n + 1 << ' ';
cout << endl;
}
return 0;
}
B.机器人走迷宫
有一个
n
×
m
n\times m
n×m 个单元格构成的迷宫,其中空单元格用.
表示,障碍物用#
表示。
迷宫中有一个机器人,它的起点位置用S
表示,目标位置用E
表示,这两个地点均没有障碍。
机器人只能沿上下左右四个方向移动。
给定一串由数字 0 ∼ 3 0\sim 3 0∼3 构成的字符串,表示机器人的行动指令列表。
机器人将按照列表中的指令,依次进行移动。
在执行指令的过程中:
如果机器人走出迷宫边界或者碰到障碍物,则机器人会损坏。
如果机器人到达目标位置,则停止行动,不再接受后续指令。
现在,哪个数字
(
0
∼
3
)
(0\sim 3)
(0∼3) 对应哪种行动(上下左右)还未分配。
请问,共有多少种分配方案,能够使得机器人顺利到达目标位置。
输入格式
第一行包含整数 T T T,表示共有 T T T 组测试数据。
每组数据第一行包含两个整数 n n n 和 m m m。
接下来
n
n
n 行,每行包含
m
m
m 个字符,表示迷宫。所有字符均为 .
, #
, S
, E
之一,其中 S
和 E
出现且仅出现一次。
最后一行包含一个字符串 s s s 表示指令列表,每个字符均为 0 ∼ 3 0\sim 3 0∼3 之一。
输出格式
每组数据输出一行结果,表示能够使得机器人顺利到达目标位置的行动指令分配方案数量。
数据范围
前三个测试点满足
2
≤
n
,
m
≤
10
2\leq n,m\leq 10
2≤n,m≤10。
所有测试点满足
1
≤
T
≤
10
1\leq T\leq 10
1≤T≤10,
2
≤
n
,
m
≤
50
2\leq n,m\leq 50
2≤n,m≤50,
1
≤
∣
s
∣
≤
100
1\leq |s|\leq 100
1≤∣s∣≤100。
同一测试点内,所有
n
×
m
n\times m
n×m 的和不超过
2500
2500
2500。
输入样例:
2
5 6
.....#
S....#
.#....
.#....
...E..
333300012
6 6
......
......
..SE..
......
......
......
01232123212302123021
输出样例:
1
14
算法:
枚举 + 全排列
判断机器人是否能够到达终点不难,思路同经典的 BFS 最短路模型判断一致、问题在于如何全排列方向。
原始的(上下左右)用0, 1, 2, 3
表示,可以构造一个数组a[]
,a[0]
表示(上),a[1]
表示下……。这样我们只需要将这个下标进行全排列即可,可以用 STL 内的next_permutation
函数全排列a[]
的元素。
对于给定的指令串path
,我们用path[i] - '0'
的方式索引对应的方向即可。
Code
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 55;
int n, m;
char g[N][N];
string path;
bool check(vector<int> &q)
{
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
int x, y;
for (int i = 0; i < n; i ++ )
for (int j = 0; j < m; j ++ )
if (g[i][j] == 'S')
x = i, y = j;
for (auto c : path)
{
int t = q[c - '0'];
x += dx[t], y += dy[t];
if (x < 0 || x >= n || y < 0 || y >= m || g[x][y] == '#')
return false;
if (g[x][y] == 'E') return true;
}
return false;
}
int main()
{
int T;
cin >> T;
while (T -- )
{
cin >> n >> m;
for (int i = 0; i < n; i ++ ) cin >> g[i];
cin >> path;
int res = 0;
vector<int> q{0, 1, 2, 3};
for (int i = 0; i < 24; i ++ )
{
if (check(q)) res ++ ;
next_permutation(q.begin(), q.end());
}
cout << res << endl;
}
return 0;
}
C.最大路径权值
给定一个 n n n 个点 m m m 条边的有向图。
图中可能包含重边和自环,也可能不连通。
给每个点分配一个小写字母。
我们定义一条路径的权值为出现频率最高的字母的出现次数。
例如,如果一条路径上的字母是 abaca,则该路径的权值为 3。
请你找到给定图中权值最大的路径,输出这个最大路径权值。
输入格式
第一行包含两个整数 n n n 和 m m m。
第二行包含一个由小写字母构成的字符串 s s s,其中第 i i i 个字母表示第 i i i 个点上的字母。
接下来 m m m 行,每行包含两个整数 x , y x,y x,y 表示存在一条从点 x x x 到点 y y y 的边。
所有点的编号为 1 ∼ n 1\sim n 1∼n。
输出格式
输出最大路径权值。
如果这个权值是无穷大,则输出 − 1 −1 −1。
数据范围
前三个测试点满足
1
≤
n
,
m
≤
10
1\leq n,m\leq 10
1≤n,m≤10。
所有测试点满足
1
≤
n
,
m
≤
3
×
105
,
1
≤
x
,
y
≤
n
1\leq n,m\leq 3\times 105,1\leq x,y\leq n
1≤n,m≤3×105,1≤x,y≤n。
输入样例1:
5 4
abaca
1 2
1 3
3 4
4 5
输出样例1:
3
输入样例2:
6 6
xzyabc
1 2
3 1
2 3
5 4
4 3
6 4
输出样例2:
-1
前置知识:
1、拓扑图和拓扑排序
2、DAG 图定义:如果一个有向图无法从任意顶点出发经过若干条边回到该点,则这个图就是有向无环图。所以一个 DAG 图是没有自环和回路的。
题目分析:
如果题目中存在自环和回路,环上所有点的字母都会出现无穷多次,就不可能存在出现次数最多的情况。
若没有自环和回路,路径长度一定是有限值,一定有解。
路径长度最大为 n n n 。
如何判断有无环存在:
常用方法:
1、Topsort
2、有向图的强连通分量
如果不存在环,怎么求最长路径?
每个点会分配一个小写字母,出现次数最多的字母,一共只有26种,而每个点的权值只有
26
26
26 种,可以考虑枚举每个字母为最大值的情况,最后求一个max
。
以a
为例
如果一个点的字母是a
,则点权为
1
1
1,否则为
0
0
0。
a - > b - > a -> c -> a
1 -> 0 -> 1 -> 0 -> 1
即在这个拓扑图上求最长路,可以用递推和 DP 来求,
对于已经求好的拓扑序列,倒序使用递推求最长路即可。
这里稍微解释一下为什么可以这么做:
首先要理解一下拓扑序列的含义,这里我们可以使用拓扑排序的方式来判断该图是否是 DAG 图,如果是,则队列q[]
中存放的就是一个拓扑序列,那么我们可以从后往前倒推,用闫氏 DP 分析法来分析:
闫氏 DP 分析法:
状态表示(在拓扑序列中):
集合: f ( t ) f(t) f(t) 表示所有以 t t t 为起点的路径构成的集合
属性:集合中所包含路径长度的最大值
状态计算:
f ( t ) = m a x ( f ( j ) + w t → j ) , j ∈ A f(t) = max(f(j) + w_{t\rightarrow j}),\ j\in A f(t)=max(f(j)+wt→j), j∈A,其中 A A A 表示点 t t t 的所有邻边构成的集合。
这样,我们在拓扑序列中从后往前递推,就可以算出权值最大的路径和最大路径权值了。
时间复杂度
拓扑排序预处理 + 26 × n 26\times n 26×n,每次递推时间复杂度为 O ( n ) O(n) O(n),故总时间复杂度为 O ( 26 ⋅ n ) O(26\cdot n) O(26⋅n)。
Code
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 300010, M = N;
int n, m;
char w[N];
int h[N], e[M], ne[M], idx;
int q[N], d[N];
int f[N]; // 每个点的最长路
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
bool topsort()
{
int hh = 0, tt = -1;
for (int i = 1; i <= n; i ++ )
if (!d[i])
q[ ++ tt] = i;
while (hh <= tt)
{
int t = q[hh ++ ];
for (int i = h[t]; ~i; i = ne[i])
{
int j = e[i];
-- d[j];
if (!d[j])
q[ ++ tt] = j;
}
}
return tt == n - 1;
}
int main()
{
scanf("%d%d", &n, &m);
memset(h, -1, sizeof h);
scanf("%s", w + 1); // 读入每个点的字母
while (m -- )
{
int a, b;
scanf("%d%d", &a, &b);
add(a, b);
++ d[b];
}
if (!topsort()) puts("-1");
else
{
int res = 0; // 答案,最大值
for (char c = 'a'; c <= 'z'; c ++ )
for (int i = n - 1; i >= 0; i -- )
{
// q[i]为当前点
int t = q[i];
// 看一下当前这个点权值为多少。
int v = w[t] == c;
// f[t] 表示以t为起点的长度的最大值
f[t] = v;
// t不一定有后继,如果有后继,则遍历所有t的出边
for (int j = h[t]; ~j; j = ne[j])
{
int k = e[j];
f[t] = max(f[t], v + f[k]);
}
res = max(res, f[t]);
}
printf("%d\n", res);
}
return 0;
}