「青训营 X 码上掘金」主题创作活动

68 阅读3分钟

当青训营遇上码上掘金

我是打acm的,本次活动,没用什么特别的设计,就简单讲一下主题三和主题四的解法以及时间复杂度分析。

主题三:寻友之旅

小青要找小码去玩,他们的家在一条直线上,当前小青在地点 N ,小码在地点 K (0≤N , K≤100 000),并且小码在自己家原地不动等待小青。小青有两种交通方式可选:步行和公交。 步行:小青可以在一分钟内从任意节点 X 移动到节点 X-1 或 X+1 公交:小青可以在一分钟内从任意节点 X 移动到节点 2×X (公交不可以向后走)

请帮助小青通知小码,小青最快到达时间是多久? 输入: 两个整数 N 和 K 输出: 小青到小码家所需的最短时间(以分钟为单位)

解法

首先,看到这题第一眼想到 dfs 暴力查找,见代码:

int startplace, endplace, mi = 1e5;
bool vis[maxn];
void dfs(int cur, int sumtime) {
    if (vis[cur]) return;
    if (sumtime > mi) return;
    if (cur < min(startplace, endplace) / 2 || cur > 2 * max(startplace, endplace)) return; // 卡范围,优化时间
    if (cur == endplace) {
        mi = min(mi, sumtime);  // 取所有路线的最短时间
        return;
    }
    vis[cur] = 1;  // 对此次查找路线经过的地方做标记,防止重复走
    dfs(2 * cur, sumtime + 1);
    dfs(cur - 1, sumtime + 1);
    dfs(cur + 1, sumtime + 1);
    vis[cur] = 0;  // 此处采用回溯法
}
int solve(){
    dfs(startplace, 0);
    return mi;  // 答案
}

但很明显,1e5 的范围内,暴力查找显然是行不通的, 若数据大的话,时间逼近于 pow(3,1e5)

也可以考虑一些贪心策略,也不太行,此时大致可以放弃 dfs ,那就只能用 bfs 试试了。

在 dfs 上,我们用过标记,在 bfs 上我们可以更进一步考虑时间,即从起点开始搜索,按照时间排序,即一个队列就可以实现(先进先出)。即先将起点 push 进去,然后起点 1 分钟到的点,接着能到的点继续走,相当于一个时间队列。再考虑每个点前面经过了,则后面经过时间自然长些,后面经过这个点的方案可以直接去除,即每个点便利一次就可以了, 做个标记,即时间复杂度为 O(n)

这个题基本就可以解决了,绕了个大圈子,发现 一个简单的 bfs 此时实用的多。额......

下面看解法:

const int maxn = 2e5 + 10;
int startplace, endplace;
int ti[maxn];  // 到达某点的时间
int solve() {
    memset(ti, -1, sizeof(ti));  // -1 为未访问过该点
    queue<int>q;
    q.push(startplace);
    ti[startplace] = 0;
    if (endplace == startplace) return 0;
    while (!q.empty()) {
        int now = q.front(); q.pop();
        if (now == endplace) return ti[endplace];  // 最先打达终点
        for (int i = 0;i < 3;i++) {
            int to;
            if (i == 0) to = now + 1;
            else if (i == 1) to = now - 1;
            else to = now * 2;
            if (to >= 0 && to < maxn) {
                if (ti[to] != -1) continue;  // 重复访问某点
                ti[to] = ti[now] + 1;  // 时间增加
                q.push(to);
            }
        }
    }
}

主题四:攒青豆

现有 n 个宽度为 1 的柱子,给出 n 个非负整数依次表示柱子的高度,排列后如下图所示,此时均匀从上空向下撒青豆,计算按此排列的柱子能接住多少青豆。(不考虑边角堆积)

攒青豆.png

以下为上图例子的解析:
输入:height = [5,0,2,1,4,0,1,0,3]  
输出:17  
解析:上面是由数组 [5,0,2,1,4,0,1,0,3] 表示的柱子高度,在这种情况下,可以接 17 个单位的青豆。
复制代码

解答:

将每一列分开来考虑,则很容易知道该列的青豆数等于 max( 0, min( leftmax, rightmax ) - h[now] ) , h[now] 为当前列的高度,leftmax 指改列左边列的最大高度值,同理 rightmax 指右边最大高度值。首先可以考虑暴力查找左右的最大值,查询需要O(n), 可用前缀最大值和右缀最大值优化,因为每次查询的区间是从最左和最右开始的,时间复杂度为 O(1)。即每一列的青豆数很容易得到,然后分别求每一列值相加即可,即为答案,总时间复杂度为 O(n),做这道题当然是 OK 的。

下面看一下关键代码:

int pre[maxn], las[maxn];
int find(vector<int> &s) {
    int n = s.size(), sum = 0;
    for (int i = 1;i <= n;i++) pre[i] = max(pre[i - 1], s[i - 1]);  // 前缀最大值
    for (int i = n;i >= 1;i--+) las[i] = max(las[i + 1], s[i - 1]);  // 后缀最大值
    for (int i = 1;i <= n;i++) {
        int leftmax = pre[i];   // 左边最大值
        int rightmax = las[i];  // 右边最大值
        int now = max(0, min(leftmax, rightmax) - s[i - 1]);  // 此列的青豆数
        sum += now;
    }
    return sum;
}

作者:梦游呢
链接:juejin.cn/post/719366…
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。