SMU Summer 2024 Contest Round 5

SMU Summer 2024 Contest Round 5

Robot Takahashi

思路

按照 \(W_i\) 排个序,算一下前缀后缀 10 的个数就行了。答案大概是一个 \(\max(ans,pre_i+suf_{i+1})\) 的形式。

排序之后当 \(W_i=W_{i+1}\) 时无法在 i,i+1 之间断开,要特判。

代码

#include<bits/stdc++.h>

using namespace std;

using i64 = long long;

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

    int n;
    cin >> n;

    string s;
    cin >> s;
    s = " " + s;

    vector<array<int, 2>> w(n + 1);
    for (int i = 1; i <= n; i ++) {
        cin >> w[i][0];
        w[i][1] = s[i] - '0';
    }

    sort(w.begin() + 1, w.end());

    vector<int> pre(n + 1), suf(n + 2);
    for (int i = 1; i <= n; i ++)
        pre[i] = pre[i - 1] + (!w[i][1]);

    for (int i = n; i >= 1; i --)
        suf[i] = suf[i + 1] + w[i][1];

    int ans = 0;
    for (int i = 0; i <= n; i ++) {
        if (w[i][0] != w[i + 1][0]) {
            ans = max(ans, pre[i] + suf[i + 1]);
        }
    }

    cout << ans << '\n';

    return 0;
}

Connect 6

题意

有一个 \(N\times N\) 的棋盘网格用 \(N\) 行字符串 \(S_i\) 来表示。如果 \(S_{i,j}\)#,说明棋盘的第 \(i\) 行第 \(j\) 列有一个棋子,否则如果 \(S_{i,j}\).,说明没有棋子。

请你判断是否可以再加入最多两个棋子使得棋盘存在六子连。六子连的定义是,存在某行、某列或者某对角线上有连续的六个棋子。

思路

暴力判断即可。

代码

#include<bits/stdc++.h>

using namespace std;

using i64 = long long;

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

    int n;
    cin >> n;

    vector<string> s(n);
    for (auto &i : s)
        cin >> i;

    int ans = 0;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            if (i + 5 < n) {                   //列
                int cnt = 0;
                for (int k = i; k <= i + 5; k++)
                    cnt += s[k][j] == '.';
                ans |= cnt <= 2;
            }
            if (j + 5 < n) {                   //行
                int cnt = 0;
                for (int k = j; k <= j + 5; k++)
                    cnt += s[i][k] == '.';
                ans |= cnt <= 2;
            }
            if (i + 5 < n && j + 5 < n) {    // 主对角线
                int cnt = 0;
                for (int k = 0; k <= 5; k++)
                    cnt += s[i + k][j + k] == '.';
                ans |= cnt <= 2;
            }
            if (i - 5 >= 0 && j + 5 < n) { // 副对角线
                int cnt = 0;
                for (int k = 0; k <= 5; k++)
                    cnt += s[i - k][j + k] == '.';
                ans |= cnt <= 2;
            }
        }
    }

    puts(ans ? "Yes" : "No");

    return 0;
}

Strange Balls

题意

高桥君收到了 \(N\) 个奇怪的球,球摆成一列,每个球的表面都写着一个数字,第 \(i\) 个球的表面数字是 \(a_i\)

高桥君准备将所有的球从 \(1\)\(N\) 依次放入桶中。桶是圆柱形的,底面是封死的,只能从圆柱形顶端放入。桶比较窄,桶中的球只能全部竖着叠放。

高桥君在放球的过程中,奇怪的事情发生了,如果桶中有连续 \(x\) 个值为 \(x\) 的球,这些球将会消失。

请你帮助高桥君计算出,从 \(1\)\(N\) 依次放入每个球后,桶中的球有多少个?

思路

用栈记录每次放入的元素,cnt 数组记录栈中栈顶值连续相同的个数,当栈顶值连续个数与值相同时,从栈中弹出相应个数即可。

代码

#include<bits/stdc++.h>

using namespace std;

using i64 = long long;

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

    int n;
    cin >> n;

    vector<int> a(n + 1);
    for (int i = 1; i <= n; i ++)
        cin >> a[i];

    vector<int> st, cnt(n + 1);

    for (int i = 1; i <= n; i ++) {
        int siz = st.size();
        if (siz && a[i] == st.back())
            cnt[siz] = cnt[siz - 1] + 1;
        else
            cnt[siz] = 1;
        st.push_back(a[i]);
        if (cnt[siz] == a[i]) {
            int k = a[i];
            while (k--)
                st.pop_back();
        }
        cout << st.size() << '\n';
    }

    return 0;
}

Linear Probing

题意

维护一个长度为 \(2^{20}\) 的,下标从 \(0\)\(2^{20}-1\) 的数列 \(a\)。初始时,数列中的每一项均为 \(-1\)。令 \(n=2^{20}\)

给定 \(q\) 次操作,每次操作内容如下:

  • 1 x:将变量 \(h\) 的值定为 \(x\)。将 \(h\) 不断加 \(1\) 直到 \(a_{h \bmod n} = -1\) 为止。令 \(a_{h \bmod n}\) 的值为 \(x\)
  • 2 x:输出 \(a_{x \bmod n}\) 的值。

思路

考虑并查集思想。

首先所有的节点父亲都是自己,一旦自己被修改了,那就让自己的父亲指向右边第一个不为 -1 的节点,注意取模与开 longlong,还有路径压缩。

代码

#include<bits/stdc++.h>

using namespace std;

using i64 = long long;

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

    const int N = 1 << 20;

    vector<i64> fa(N), a(N, -1);
    iota(fa.begin(), fa.end(), 0);

    auto find = [&](auto & self, int x)->int{
        return fa[x] == x ? x : fa[x] = self(self, fa[x]);
    };

    int q;
    cin >> q;
    while (q --) {
        i64 op, x;
        cin >> op >> x;
        if (op == 1) {
            int y = find(find, x % N);
            a[y] = x;
            fa[y] = find(find, (y + 1) % N);
        } else {
            cout << a[x % N] << '\n';
        }
    }


    return 0;
}

Red Polyomino

题意

给你边长为 N 的且仅由字符 #. 组成的正方形阵列,其中 # 表示黑色格子, . 表示白色格子。你需要在白色格子中选择 K 个涂成红色,且使红色格子互相连接(仅包括上下左右相邻),求有多少种可能的方案。

思路

\(N\times N\) 的矩阵中选择 \(K\) 个,最多有 \(C_{64}^8\) 种,考虑暴力。

暴力需要一点技巧,常规暴力是从红色方块向四周搜索,但这样会因为枚举的方向而使答案一直不对,需要反过来想通过 n 方遍历地图从空白格子向四周找红色方块。

代码

#include<bits/stdc++.h>

using namespace std;

using i64 = long long;

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

    int n, k;
    cin >> n >> k;

    vector<string> s(n);
    for (auto &i : s) cin >> i;

    int ans = 0;
    const int u[] = {1, -1, 0, 0};
    const int v[] = {0, 0, 1, -1};
    auto solve = [&](auto & self, int num) {
        if (num == k) {
            ans ++;
            return ;
        }

        vector<pair<int, int>> loc;
        for (int l = 0; l < n; l ++)
            for (int c = 0; c < n; c++) {
                for (int i = 0; i < 4; i ++) {
                    if (s[l][c] == '.') {
                        int dx = l + u[i];
                        int dy = c + v[i];
                        if (dx >= 0 && dx < n && dy >= 0 && dy < n && s[dx][dy] == 'r' ) {
                            s[l][c] = 'r';
                            self(self, num + 1);
                            s[l][c] = '#';
                            loc.push_back({l, c});
                        }
                    }

                }
            }
        for (auto [x, y] : loc)
            s[x][y] = '.';

    };

    for (int i = 0; i < n; i ++) {
        for (int j = 0; j < n; j ++) {
            if (s[i][j] == '.') {
                s[i][j] = 'r';
                solve(solve, 1);
                s[i][j] = '#';
            }
        }
    }

    cout << ans << '\n';

    return 0;
}

Stronger Takahashi

题意

有一个城镇被划分为H行和W列的单元格网格。

如果 \(S_i,_j\) 是' . ',则为道路;如果 \(S_i,_j\) 为' # ',则为障碍物。

高桥将从家里去鱼市。他的房子在左上角的方格,鱼市在右下角的方格。

高桥可以从一个单元格向上、向下、向左或向右移动到可通过的单元格。他不能离开小镇,也不能进入街区。

但是,他可以一次摧毁一个 2$\times$2 正方形区域中的所有障碍物,使这个区域可以通过,但需要消耗一点能量。

找到高桥进入鱼市需要消耗的最少能量。

思路

不使用能量时,转移消耗为 0 ,使用能量时,消耗 1 能量,则为典型的 01 bfs,使用双端队列跑 bfs 即可。

代码

#include<bits/stdc++.h>

using namespace std;

using i64 = long long;

int dx1[4][2] = {{0, -1}, { -1, 0}, {1, 0}, {0, 1}};
int dx2[25][2] = {{ -1, -1}, {1, 1}, { -1, 1}, {1, -1},
    {2, -1}, {2, 1}, {1, 2}, {1, -2}, { -2, 1}, { -2, -1},
    { -1, -2}, { -1, 2}, {2, 0}, {0, 2}, { -2, 0}, {0, -2}
};

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

    int n, m;
    cin >> n >> m;

    vector<string> s(n);
    for (auto &i : s)
        cin >> i;

    vector dis(n, vector<i64>(m, INT_MAX));
    deque<array<int, 2>> Q;

    Q.push_front({0, 0});
    dis[0][0] = 0;

    while (Q.size()) {
        auto [x, y] = Q.front();
        Q.pop_front();

        for (int i = 0; i < 4; i ++) {
            int dx = x + dx1[i][0], dy = y + dx1[i][1];
            if (dx >= 0 && dx < n && dy >= 0 && dy < m && s[dx][dy] == '.') {
                if (dis[dx][dy] > dis[x][y]) {
                    dis[dx][dy] = dis[x][y];
                    Q.push_front({dx, dy});
                }
            }
        }

        for (int i = 0; i < 16; i ++) {
            int dx = x + dx2[i][0], dy = y + dx2[i][1];
            if (dx >= 0 && dx < n && dy >= 0 && dy < m) {
                if (dis[dx][dy] > dis[x][y] + 1) {
                    dis[dx][dy] = dis[x][y] + 1;
                    Q.push_back({dx, dy});
                }
            }
        }
    }

    cout << dis[n - 1][m - 1] << '\n';

    return 0;
}

Predilection

题意

有一个长度为 N 的数列 A。

你可以进行若干次,最多 N−1 次操作,选择相邻的两个数,删去他们,并在原位置放上他们两个的和。

现在你需要求出可能产生的序列个数。

思路

考虑 dp。

考虑对这个数组进行前缀和操作,用 \(num_i\) 表示第 𝑖 个数,则 \(𝑎_𝑖=∑_{𝑗=1}^𝑖𝑛𝑢𝑚_𝑗\)。我们发现合并第 𝑖 和 𝑖+1 个数合并就是第 𝑖 和 𝑖+1 个数后进行前缀和操作,实际上就是删除了 \(𝑎_𝑖\)。对于合并第 𝑖 和 𝑖+1 个数的操作,𝑖 最多取到 𝑛−1,即 𝑖 < 𝑛,从中得出在删除操作中,\(𝑎_𝑛\) 必须保留。因为每个前缀和只能对应一个数,于是题意简化成:对给出的 𝑛 个数进行前缀和操作,每次可以删除任意一个 \(𝑎_𝑖(𝑖<𝑛)\),求共有多少种不同的序列。可以发现,实际上就是求前缀和数组 \(𝑎_1∼𝑎_{𝑛−1}\) 的不同子序列的个数。

  • 若此数之前出现过,\(dp_𝑖=dp_{𝑖−1}\times 2−dp_{𝑙𝑎𝑠𝑡-1}\) 这里是因为我们若接上上一个出现过的所有东西都会和上一个接上重复。

  • 否则,\(dp_𝑖=dp_{𝑖−1}×2+1\) 这里先接上前面的所有在加上自己。

最后输出 \(dp_{n-1}+1\)。(加上空子序列)。

代码

#include<bits/stdc++.h>

using namespace std;

using i64 = long long;

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

    int n;
    cin >> n;

    const int mod = 998244353;

    vector<i64> a(n + 1);
    for (int i = 1; i <= n; i ++) {
        cin >> a[i];
        a[i] += a[i - 1];
    }

    map<i64, int> mp;
    vector<i64> dp(n + 1);
    dp[1] = 1, mp[a[1]] = 1;

    for (int i = 2; i <= n; i ++) {
        int last = mp[a[i]];
        if (!last) dp[i] = (dp[i - 1] * 2 + 1) % mod;
        else dp[i] = (dp[i - 1] * 2 % mod - dp[last - 1] + mod) % mod;
        mp[a[i]] = i;
    }

    cout << dp[n - 1] + 1 << '\n';

    return 0;
}
posted @ 2024-07-19 21:57  Ke_scholar  阅读(45)  评论(0编辑  收藏  举报