2022 RoboCom 世界机器人开发者大赛-本科组(国赛)

写在前面:最后一题比较有意思,使用了bitset优化,没想到Rc也喜欢考。

RC-u1 智能红绿灯

思路:

签到题。根据题目要求打好标记就好了,只需要注意一些corner case,如刚按下按钮的时刻、红灯变绿灯的时刻以及绿灯变红灯的时刻。

时间复杂度: O ( m a x ( t i m e ) + N ) O(max(time) + N) O(max(time)+N)

代码:

#include <bits/stdc++.h>

using i64 = long long;

int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);

    int N;
    std::cin >> N;

    std::vector<int> a(N);
    for (int i = 0; i < N; i++) {
        std::cin >> a[i];
    }

    int ok = 0;
    int cur = 0;
    int nxt1 = -1, nxt2 = -1;
    // std::vector<int> stk;
    std::vector<std::pair<int, int>> ans;
    for (int time = 0; time <= 1E5; time++) {
        if (time == nxt1) {
            // stk.push_back(time + 15);
            ok = 2;
        }
        while (cur < N && a[cur] == time) {
            if (!ok) {
                ok = 1;
                nxt1 = time + 15;
                nxt2 = time + 44;
            } else if (ok == 2) {
                ok = 3;
                nxt2 += 15;
            }
            cur++;
        }
        if (time == nxt2) {
            ans.push_back({nxt1, nxt2});
            nxt2 = -1;
            nxt1 = -1;
            ok = 0;
        }
    }

    for (auto [x, y] : ans) {
        std::cout << x << " " << y << "\n";
    }
    return 0;   
}

RC-u2 女王的大敕令

思路:注意到小怪只会攻击我们两次,所以实际上我们只要枚举两次状态就好了,在这之前我们需要计算出小怪会攻击的行和列,同时也要判断枚举的状态是否满足步长。

时间复杂度: O ( 5 4 ) O(5^4) O(54)

代码:

#include <bits/stdc++.h>

using i64 = long long;

int distance(int x1, int y1, int x2, int y2) {
    return std::abs(x1 - x2) + std::abs(y1 - y2);
}
int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);

    int C1, C2, R1, R2;
    std::cin >> C1 >> C2 >> R1 >> R2;
    C1--, C2--, R1--, R2--;

    std::vector<int> n(4);
    for (int i = 0; i < 4; i++) {
        std::cin >> n[i];
    }

    int row, col, D1, D2;
    std::cin >> row >> col >> D1 >> D2;
    row--, col--;

    std::array<int, 4> first{C1, C2, R1 - n[2], R2 + n[3]}, second{C1 + n[0], C2 - n[1], R1 - n[2], R2 + n[3]};

    for (int x1 = 0; x1 < 5; x1++) {
        for (int y1 = 0; y1 < 5; y1++) {
            for (int x2 = 0; x2 < 5; x2++) {
                for (int y2 = 0; y2 < 5; y2++) {
                    if (distance(x1, y1, x2, y2) == D1 && distance(x2, y2, row, col) == D2) {
                        if (y1 == first[1] || y1 == first[0] || x1 == first[3] || x1 == first[2]) {
                            continue;
                        }
                        if (y2 == second[1] || y2 == second[0] || x2 == second[3] || x2 == second[2]) {
                            continue;
                        }

                        std::cout << x1 + 1 << " " << y1 + 1 << " " << x2 + 1 << " " << y2 + 1 << "\n";
                    }
                }
            }
        }
    }
    return 0;   
}

RC-u3 战利品分配

思路:注意到边权都为1,那么我们可以直接进行bfs,进而就能获取到从源点S到汇点T的所有路径。读者可以考虑为什么不使用Dijkstra。这样再队列中额外加入价值这一属性就可以进行求解了。需要注意的是,本题也有corner case,如玩家排在第一位,就需要将源点S的价值加进来,另外还需要注意记录最短路径的值(因为有多条到汇点T的路径)。

时间复杂度: O ( N + M ) O(N + M) O(N+M)

代码:

#include <bits/stdc++.h>

using i64 = long long;

int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);

    int N, M, K, P;
    std::cin >> N >> M >> K >> P;
    P--;

    std::vector<int> p(N);
    for (int i = 0; i < N; i++) {
        std::cin >> p[i];
    }    

    std::vector<std::vector<int>> adj(N);
    for (int i = 0; i < M; i++) {
        int U, V;
        std::cin >> U >> V;
        U--, V--;

        adj[U].push_back(V);
        adj[V].push_back(U);
    }

    int S, T;
    std::cin >> S >> T;
    S--, T--;

    std::vector<int> dis(N, -1);
    std::queue<std::pair<int, i64>> q;
    q.push({S, (P == 0 ? p[S] : 0)});
    dis[S] = 0;
    i64 ans = 0;
    int minDis = -1;
    while (!q.empty()) {
        auto [x, val] = q.front();
        q.pop();

        if (x == T && dis[x] == minDis) {
            ans = std::max(ans, val);            
            continue;
        }
        for (auto y : adj[x]) {
            if (dis[y] == -1 || y == T) {   
                if (y == T) {
                    if (dis[y] == -1) {
                        minDis = dis[x] + 1;
                    } else if (dis[x] + 1 != minDis) {
                        continue;
                    }
                }
                dis[y] = dis[x] + 1;
                if (dis[y] % K == P) {
                    q.push({y, val + p[y]});
                } else {
                    q.push({y, val});
                }
            }
        }
    }

    std::cout << ans << "\n";
    return 0;   
}

RC-u4 变牛的最快方法

思路:其实这是一道最短编辑距离的板子题,只不过本题需要记录每一步的操作。记dp[i][j]为A序列前i位变为B序列前j位的最短编辑距离,op[i][j]就为每一步对应的操作,pre[i][j]代表dp[i][j]是从哪个状态转移过来的,用于后续输出具体方案。

下面考虑状态转移:对于删除操作,说明A的前i - 1位已经和B的j位匹配了,故dp[i][j] = dp[i - 1][j] + 1;插入操作,A的前i位已经和B的j - 1位匹配,故dp[i][j] = dp[i][j - 1] + 1;修改操作,A的前i - 1位已经和B的j - 1位匹配,故dp[i][j] = dp[i - 1][j - 1] + (A[i] == B[j])。三种情况取最小值即可。

接下来就是考虑初始化问题了,对于dp[0][i]dp[i][0]是需要我们进行初始化的,oppre也是如此。

时间复杂度: O ( N M ) O(NM) O(NM)

代码:

#include <bits/stdc++.h>

using i64 = long long;

constexpr int inf = 1E9;
int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);

    std::vector<int> A{0}, B{0};
    int x;
    while (std::cin >> x) {
        if (x == -1) {
            break;
        }
        A.push_back(x);
    }    

    while (std::cin >> x) {
        if (x == -1) {
            break;
        }
        B.push_back(x);
    }    

    int N = A.size();
    int M = B.size();
    std::vector dp(N, std::vector<int>(M, inf));
    std::vector op(N, std::vector<int>(M));
    std::vector pre(N, std::vector<std::pair<int, int>>(M));

    for (int i = 0; i < N; i++) {
        dp[i][0] = i;
        op[i][0] = 0;
        if (i) {
            pre[i][0] = {i - 1, 0};
        }
    }
    for (int i = 0; i < M; i++) {
        dp[0][i] = i;
        op[0][i] = 3;
        if (i) {
            pre[0][i] = {0, i - 1};
        }
    }

    for (int i = 1; i < N; i++) {
        for (int j = 1; j < M; j++) {
            int add = dp[i][j - 1] + 1;
            int del = dp[i - 1][j] + 1;
            int modify = dp[i - 1][j - 1] + !(A[i] == B[j]);
            dp[i][j] = std::min({add, del, modify});

            if (dp[i][j] == add) {
                op[i][j] = 3;
                pre[i][j] = {i, j - 1};
            } else if (dp[i][j] == del) {
                op[i][j] = 0;
                pre[i][j] = {i - 1, j};
            } else {
                op[i][j] = (A[i] == B[j]) ? 2 : 1;
                pre[i][j] = {i - 1, j - 1};
            }
        }
    }

    std::cout << dp[N - 1][M - 1] << "\n";

    std::vector<int> ans;
    std::pair<int, int> state = {N - 1, M - 1};
    while (1) {
        auto [lhs, rhs] = state;
        if (!lhs && !rhs) {
            break;
        }
        // debug(state);
        ans.push_back(op[lhs][rhs]);
        state = pre[lhs][rhs];
    }

    std::reverse(ans.begin(), ans.end());
    for (auto x : ans) {
        std::cout << x;
    }
    return 0;   
}

RC-u5 养老社区

思路:首先看到题目给的数据是一颗树,因此可以在 O ( n 2 ) O(n^2) O(n2)的时间复杂度内将树上任意两点间的距离求出来。一个比较暴力的想法是我们可以 O ( n 3 ) O(n^3) O(n3)地枚举每三个点的组合,本题由于数据比较水并没有卡这种做法,所以实际上是可以跑过去的。

我们考虑优化,出于经验,这类多层循环暴力枚举的问题我们或许可以进行bitset优化,那么时间复杂度降为 O ( n 3 w ) O(\frac{n^3}{w}) O(wn3),可以轻松通过该题目。

考虑可行性:直观上想,我们先可以枚举两个点,剩下的一个点使用bitset进行优化,即使用bitset来刻画“对于某一点,剩下的点中可用点的集合”,本题是有两个限定条件的,即长度与种类,所以实际上需要开两种bitset。由于长度限制,我们将当前正枚举的两个点按照长度放到不同数组中,对于每一个长度,f1[u] & f1[v] & f[T[u]] & f[T[v]]中1的个数(集合大小)即为答案,除以3得到最终答案,因为三元组被重复计算了三遍。

时间复杂度: O ( n 3 w ) ,其中 ( w = 64 ) O(\frac{n^3}{w}),其中(w=64) O(wn3),其中(w=64)

代码:

#include <bits/stdc++.h>

using i64 = long long;

int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);

    int N;
    std::cin >> N;

    std::vector<std::vector<int>> adj(N);
    for (int i = 0; i < N - 1; i++) {
        int U, V;
        std::cin >> U >> V;
        U--, V--;

        adj[U].push_back(V);
        adj[V].push_back(U);
    }

    constexpr int M = 2E3;
    std::bitset<M> f[N];
    for (int i = 0; i < N; i++) {
        f[i] = ~f[i];
    }
    std::vector<int> T(N);
    for (int i = 0; i < N; i++) {
        std::cin >> T[i];
        T[i]--;
        f[T[i]].set(i, false);
    }
    std::vector dis(N, std::vector<int>(N, -1));
    for (int i = 0; i < N; i++) {
        std::queue<int> q;
        q.push(i);
        dis[i][i] = 0;

        while (!q.empty()) {
            int x = q.front();
            q.pop();

            for (auto y : adj[x]) {
                if (dis[i][y] == -1) {
                    dis[i][y] = dis[i][x] + 1;
                    q.push(y);
                }
            }
        }
    }

    std::vector<std::vector<std::pair<int, int>>> S(N + 1);
    for (int i = 0; i < N; i++) {
        for (int j = i + 1; j < N; j++) {
            S[dis[i][j]].push_back({i, j});
        }
    }

    i64 ans = 0;
    for (int d = 0; d <= N; d++) {
        std::bitset<M> f1[N];
        for (auto [u, v] : S[d]) {
            f1[u].set(v, true);
            f1[v].set(u, true);
        }

        for (auto [u, v] : S[d]) {
            if (T[u] == T[v]) {
                continue;
            }

            ans += (f1[u] & f1[v] & f[T[u]] & f[T[v]]).count();
        }

    }

    std::cout << ans / 3 << "\n";
    return 0;
}
  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值