美团2017年CodeM大赛-资格赛 NC13224 送外卖(记忆化搜索 or 预处理 dfs)

在这里插入图片描述
在这里插入图片描述

题意:

有一个人 初始位置为 0,他的 目的地是 n - 1

给定两个表示 移动方案 的整数数列 a[0] ~ a[n-1]b[0] ~ b[n-1] ,在 每个小区 i 里他有 两种选择

    1. 选择 a向前 a[i] 个小区
    1. 选择 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 表示 0n - 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;
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值