有一个人 初始位置为 0
,他的 目的地是 n - 1
。
给定两个表示 移动方案 的整数数列 a[0] ~ a[n-1]
和 b[0] ~ b[n-1]
,在 每个小区 i
里他有 两种选择:
a
:向前 a[i]
个小区。b
:向前 b[i]
个小区。把每步的选择写成一个 关于字符 ‘a
’ 和 ‘b
’ 的字符串。求这人 到达小区 n - 1
的方案中,字典序最小 的字符串
No solution!
”。Infinity!
”。首先一个很重要的点,题目虽然要求求出 字典序最小 的答案,但我们并不需要可以去求,因为在本题中,以 中序遍历 dfs
的顺序 遍历所有方案时,我们 得到的第一个答案一定字典序最小。
本题数据范围达到了 1e5
,每一步有 两种选择,如果敲裸的 dfs
的话,复杂度达到了 2 ^ 1e5
,这显然是不被允许的。考虑进行 两遍 dfs
,第一遍 dfs
预处理出从起点 0
出发,能够到达终点 n - 1
的所有方案中可途径的点,用一个 bool
数组 ok
来存储信息,ok[u] = true
表示 从 0
到 n - 1
的某个方案中途径了 u
点。(由于有 标记数组 st
防止重复搜索,时间复杂度为 O(N)
)
void dfs1(int u = 1)
{
if (st[u]) return; //搜过无须再搜
st[u] = true; //必要的防止重复搜索标记
if (u == n) { //一旦到达终点,标记为 true
ok[u] = true;
return;
}
int to;
to = u + a[u]; if (to >= 1 && to <= n) dfs1(to), ok[u] |= ok[to]; //回溯的时候将某条合法路径上所有点标记为通过该点可达终点
to = u + b[u]; if (to >= 1 && to <= n) dfs1(to), ok[u] |= ok[to];
}
对于 第二遍 dfs
,我们就可以开始记录方案了,中序遍历 dfs
第一次达到终点的方案,即为 字典序最小的方案,输出该字符串即可,前提是保证无环。
如果搜索过程中 搜索到了环,由于 第一遍 dfs
已经完成了 预处理 ok
数组 的操作,因此这个环也是一个 合法方案,且 字典序最小,根据题意 直接输出“Infinity!
” 即可。(时间复杂度 也为:O(N)
)
void dfs2(int u = 1)
{
if (st[u]) { //重复搜索到同一个位置,即搜索到了环,而且是个字典序最小的合法环
puts("Infinity!");
exit(0);
}
st[u] = true;
if (u == n) { //搜到终点 由于之前完成了预处理,这个方案即为字典序最小方案,输出
cout << path << '\n';
exit(0);
}
int to;
to = u + a[u]; if (to >= 1 && to <= n && ok[to]) path += "a", dfs2(to); //选择 a 方案,到达的点 to 在范围内,且通过节点 to 可达终点(ok[to] == true),就加入 a 方案,继续搜索
to = u + b[u]; if (to >= 1 && to <= n && ok[to]) path += "b", dfs2(to);
}
注意:
st
数组在 两次 dfs
的过程中 有着不同的用途,第一次 用于 防止重复搜索,第二次 用于 搜环。ok[1] = true
表示 从起点 1
开始可以通过某个方案走到终点 n
,第一次 dfs
完以后,如果 不满足 ok[1] = true
则直接表示 没有合法方案,输出 “No solution!
”#include <bits/stdc++.h>
using namespace std;
//#define map unordered_map
//#define int long long
const int N = 1e5 + 10;
int a[N], b[N];
int n;
bool st[N], ok[N];
string path;
void dfs1(int u = 1)
{
if (st[u]) return;
st[u] = true;
if (u == n) {
ok[u] = true;
return;
}
int to;
to = u + a[u]; if (to >= 1 && to <= n) dfs1(to), ok[u] |= ok[to];
to = u + b[u]; if (to >= 1 && to <= n) dfs1(to), ok[u] |= ok[to];
}
void dfs2(int u = 1)
{
if (st[u]) {
puts("Infinity!");
exit(0);
}
st[u] = true;
if (u == n) {
cout << path << '\n';
exit(0);
}
int to;
to = u + a[u]; if (to >= 1 && to <= n && ok[to]) path += "a", dfs2(to);
to = u + b[u]; if (to >= 1 && to <= n && ok[to]) path += "b", dfs2(to);
}
signed main()
{
cin >> n;
for (int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
}
for (int i = 1; i <= n; ++i) {
scanf("%d", &b[i]);
}
dfs1();
if (ok[1]) {
fill(st, st + n + 1, false);
dfs2();
}
else {
puts("No solution!");
}
return 0;
}
上方的代码完全可以写成记忆化的形式,代码更加简短,不过可能没那么好理解。
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int n, a[N], b[N];
bool st[N], inf[N], ok;
string path;
bool dfs(int x)
{
if (x < 0 || x >= n) return false;
if (st[x]) {
inf[x] = true;
return false;
}
if (x == n - 1) {
return true;
}
st[x] = true;
if (dfs(x + a[x])) {
path += "a";
if (inf[x]) ok = true;
return true;
}
if (dfs(x + b[x])) {
path += "b";
if (inf[x]) ok = true;
return true;
}
return false;
}
int main()
{
cin >> n;
for (int i = 0; i < n; ++i) {
scanf("%d", &a[i]);
}
for (int i = 0; i < n; ++i) {
scanf("%d", &b[i]);
}
if (dfs(0)) {
if (ok) {
puts("Infinity!");
}
else {
reverse(path.begin(), path.end());
cout << path << '\n';
}
}
else {
cout << "No solution!" << '\n';
}
return 0;
}