Atcoder ABC 370题解

前言

本题解部分思路来源于网络,仅供参考。

A - Raise Both Hands

题目大意

如果 $ Snuck $ 想吃章鱼烧则只举起左手,如果不想吃章鱼烧则只举起右手。如果同时举起或放下两只手则输出 Invalid

解题思路

根据题意模拟即可。

code

#include <bits/stdc++.h>
using namespace std;
int main() {
    int l, r;
    scanf("%d%d", &l, &r);
    if (l == r) puts("Invalid");
    else if (l == 1) puts("Yes");
    else puts("No");
    return 0;
}

B - Binary Alchemy

题目大意

给定一个 \(N\) 种元素,将元素 \(1,\ 2,\ ...\ ,N\) 合并后得到的结果是什么?

  • \(i,j\) 元素合并时,如果 \(i \geqslant j\) ,则合并结果为 \(A_{i,j}\) ;否则合并结果为 \(A_{j,i}\)

解题思路

根据题意模拟即可。

code

#include <bits/stdc++.h>
using namespace std;
int a[105][105];
int main() {
    int n;
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= i; j++) {
            scanf("%d", &a[i][j]);
        }
    }
    int ans = a[1][1];
    for (int i = 2; i <= n; i++) {
        if (ans < i) ans = a[i][ans];
        else ans = a[ans][i];
    }
    printf("%d\n", ans);
    return 0;
}

C - Word Ladder

题目描述

给定两个字符串 \(S\)\(T\) ,求出如何从 \(S\) 变换到 \(T\) ,输出字典序最小的结果。

解题思路

因为要输出字典序最小的结果,所以可以每次选取最小的可能的结果,最后的结果也会是最小的。应为 \(S\)\(T\) 的长度最多为 \(100\) ,所以这个程序不需要任何优化。时间复杂度

#include <bits/stdc++.h>
using namespace std;
vector<string> vec;
int main() {
    string s, t;
    cin >> s >> t;
    int n = s.size();
    string tmp, tt;
    while (s != t) {
        tmp = "";
        for (int i = 0; i < n; i++) {
            tmp += 'z';
        }
        for (int i = 0; i < n; i++) {
            if (s[i] != t[i]) {
                tt = s;
                tt[i] = t[i];
                tmp = min(tmp, tt);
            }
        }
        s = tmp;
        vec.push_back(tmp);
    }
    cout << vec.size() << endl;
    for (string i : vec) {
        cout << i << endl;
    }
    return 0;
}

D - Cross Explosion

题目描述

给定一个 \(H \times W\) 的网格,最开始每一个格子上都有墙,将在某些位置按顺序投放炸弹。如果投放炸弹的地方有墙,则炸掉这堵墙。否则,炸掉上下左右看到的第一堵墙。

解题思路

因为上下左右爆炸有四个方向,所以可以看成在四个 1D 空间爆炸。对于每一个 1D 空间,我们需要一个数据结构

  • 快速地找到下一堵墙。

  • 快速的删除一堵墙。

因为这两个特性,应到我们想到 set。所以我们可以对于每一行每一列维护两个 set ,一个正向排序一个逆向排序。之后,这个问题就变成模拟了。

code

#include <bits/stdc++.h>
using namespace std;
set<int> T[400005], L[400005];
set<int, greater<>> B[400005], R[400005];
void del(int r, int c) {
    T[r].erase(c);
    B[r].erase(c);
    L[c].erase(r);
    R[c].erase(r);
}
int main() {
    int h, w, q;
    scanf("%d%d%d", &h, &w, &q);
    for (int i = 1; i <= h; i++) {
        for (int j = 1; j <= w; j++) {
            T[i].insert(j);
            B[i].insert(j);
            L[j].insert(i);
            R[j].insert(i);
        }
    }
    int r, c;
    int ans = h * w;
    set<int>::iterator ia, ic;
    set<int, greater<>>::iterator ib, id;
    while (q--) {
        scanf("%d%d", &r, &c);
        if (T[r].find(c) != T[r].end()) {
            del(r, c);
            ans--;
        } else {
            ia = T[r].upper_bound(c);
            ib = B[r].upper_bound(c);
            ic = L[c].upper_bound(r);
            id = R[c].upper_bound(r);
            if (ia != T[r].end()) {
                ans--;
                del(r, *ia);
            }
            if (ib != B[r].end()) {
                ans--;
                del(r, *ib);
            }
            if (ic != L[c].end()) {
                ans--;
                del(*ic, c);
            }
            if (id != R[c].end()) {
                ans--;
                del(*id, c);
            }
        }
    }
    printf("%d\n", ans);
    return 0;
}

E - Avoid K Partition

题目大意

给定一个序列 \(A=(A_1,A_2,\ ...\ ,A_n)\) ,求有多少种方案分割序列使得分割出来的每一个序列的总和不等于 \(K\)

解题思路

容易想到一种朴素解法:

  • 定义 \(dp_i\) 为前 \(i\) 个数的最大分割方案数。则动态转移方程为 \(dp_i = \sum_{1 \leqslant j \leqslant i,sum(j + 1,i)\neq K}dp_j\)

动态转移方程中的 \(sum(j + 1,i)\) 可以用前缀和优化。令 \(sum[i]\) 为前 \(i\) 个数的和,则动态转移方程变为 \(dp_i = \sum_{1 \leqslant j \leqslant i,sum[i] - sum[j]\neq K}dp_j\) 。或者说,要求出满足 \(sum[j] \neq sum[i] - K\)\(dp_j\) 的和。为了求出满足这个条件的数,我们令 \(cnt[i] = \sum_{sum[j]=i} dp[j]\) ,则动态转移方程变为 \(dp_i = \sum_{1 \leqslant j \leqslant i}dp_j - cnt[sum[i] - K]\) 。这样,时间复杂度就降下来了。

code

#include <bits/stdc++.h>
#define MOD 998244353
using namespace std;
using ll = long long;
int a[200005];
int main() {
    ll n, k;
    scanf("%lld%lld", &n, &k);
    for (int i = 1; i <= n; i++) {
        scanf("%d", a + i);
    }
    map<ll, int> m;
    m[0] = 1;
    ll summ = 0, sum = 1, tmp;
    for (int i = 1; i <= n; i++) {
        summ += a[i];
        tmp = (sum - m[summ - k] + MOD) % MOD;
        m[summ] = (m[summ] + tmp) % MOD;
        sum = (sum + tmp) % MOD;
    }
    printf("%lld\n", tmp);
    return 0;
}

F - Cake Division

题目大意

有一块蛋糕,上面有\(n\) 条分割线。从中选取 \(k\) 个分割线把蛋糕切开,将分割出来的 \(k\) 块蛋糕分给 \(k\) 个人。求分到蛋糕最少的那个人最多可以分到多少?该怎么分?

解题思路

“求分到蛋糕最少的那个人最多可以分到多少” 这句话引导我们想到二分查找。所以我们用二分查找的话,问题就变成了“求分到蛋糕最少的人蛋糕重量有没有 \(x\) ”。那么,应该如何求出这个问题呢?首先,套路性的化环为链,然后对于每一种可能,求出当每一块蛋糕的重量都达到 \(x\) 时,蛋糕数量有没有达到 \(k\) 。为了快速求出这个问题,我们可以使用倍增。令 \(up[i][j]\) 为以第 \(i\) 位开头,分了 \(2^j\) 块重量大于等于 \(x\) 的蛋糕需要分割到第几条分割线。如果 \(up[i][j]\) 小于 \(i + n\) ,那么从 \(i\) 开始,每块蛋糕重量大于 \(x\) 时可行的。否则就是不可行的。我们只需要求出有多少种方案,我们就可以知道分到蛋糕最少的人蛋糕重量有没有 \(x\) 和如果分到蛋糕最少的人蛋糕重量有 \(x\) 则需要切几刀。

code

#include <bits/stdc++.h>
using namespace std;
int a[400010];
int n, k;
int N;
int check(int x) {
    vector<array<int, 25>> up(N + 2);
    up[N + 1][0] = N + 1;
    queue<int> window;
    int r = 1, sum = 0;
    for (int i = 1; i <= N; i++) {
        while (r <= N && sum < x) {
            window.push(a[r]);
            sum += a[r];
            r++;
        }
        if (sum < x) up[i][0] = N + 1;
        else up[i][0] = r;
        sum -= window.front();
        window.pop();
    }
    for (int i = 1; i <= 20; i++) {
        for (int j = 1; j < N + 2; j++) {
            up[j][i] = up[up[j][i - 1]][i - 1];
        }
    }
    int cnt = 0;
    for (int i = 1; i <= n; i++) {
        int pos = i;
        for (int j = 0; j <= 20; j++) {
            if ((k >> j) & 1) {
                pos = up[pos][j];
            }
        }
        if (pos <= i + n) {
            ++cnt;
        }
    }
    return cnt;
}
int main() {
    scanf("%d%d", &n, &k);
    for (int i = 1; i <= n; i++) {
        scanf("%d", a + i);
        a[i + n] = a[i];
    }
    N = 2 * n;
    int l = 1, r = 2e9 + 8;
    while (l + 1 < r) {
        int mid = l + (r - l) / 2;
        if (check(mid)) l = mid;
        else r = mid;
    }
    int cnt = check(l);
    printf("%d %d\n", l, n - cnt);
    return 0;
}
posted @ 2024-09-08 21:55  sxl701817  阅读(112)  评论(0)    收藏  举报