AtCoder Beginner Contest 311

这篇具有很好参考价值的文章主要介绍了AtCoder Beginner Contest 311。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

A - First ABC (abc311 A)

题目大意

给定一个字符串,问最短的一个前缀,包含A B C这三个字符。

解题思路

注意到这个前缀的末尾字母一定是这三个字母中的一个,因此答案就是这三个字母出现位置最早的最大值。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int n;
    string s;
    cin >> n >> s;
    cout << max({s.find('A'), s.find('B'), s.find('C')}) + 1 << '\n';

    return 0;
}



B - Vacation Together (abc311 B)

题目大意

给定\(n\)个人的\(d\)天的空闲与否的情况,问最长的连续天,这\(n\)个人都空闲。

解题思路

我们找到第一个大家都空闲的,以此为左端点,找满足条件的最远的右端点,这作为一个候选答案。

接下来的左端点就不要\(+1\),而是改成从右端点右边继续找,因为\(+1\)开始的情况不会比刚才最优。

最终答案就是这些候选答案的最大值,双指针法,复杂度是\(O(n^2)\)

因为数据规模不大,判断某天是否所有人都空闲时,直接遍历即可。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int n, d;
    cin >> n >> d;
    vector<string> s(n);
    for (auto& i : s)
        cin >> i;
    int ans = 0;
    auto ok = [&](int day) {
        int free = true;
        for (auto& i : s)
            free &= (i[day] == 'o');
        return free;
    };
    for (int i = 0; i < d; ++i) {
        int j = i;
        while (j < d && ok(j))
            ++j;
        ans = max(ans, j - i);
        if (j != i)
            --j;
        i = j;
    }
    cout << ans << '\n';

    return 0;
}



C - Find it! (abc311 C)

题目大意

给定多个基环内向树,找一个环。

解题思路

题意的构图方式实际上是一棵或多棵基环内向树,其特点是从任意一个点出发,必定会跑到环内。

因此随便从一个点进行\(DFS\),记录一下每个点是否被访问过,当一个点第二次访问时,它就是环上的点。

然后从它遍历一下得到环上的点即可。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int n;
    cin >> n;
    vector<int> to(n);
    for (int i = 0; i < n; ++i) {
        int v;
        cin >> v;
        --v;
        to[i] = v;
    }
    int u = 0;
    vector<int> visit(n);
    for (u = 0; !visit[u]; u = to[u]) {
        visit[u] = 1;
    }
    vector<int> ans{u};
    for (int i = to[u]; i != u; i = to[i]) {
        ans.push_back(i);
    }
    cout << ans.size() << '\n';
    for (auto& i : ans)
        cout << i + 1 << ' ';
    cout << '\n';

    return 0;
}



D - Grid Ice Floor (abc311 D)

题目大意

给定一张二维网格,外围是石头,里面有若干个石头,其余都是冰面,从左上出发,选一个方向前进,会一直前进直到碰到石头。停下来,然后可以继续选一个方向前进。

问能访问到的格子数。

解题思路

考虑能访问到的格子\((i,j)\)的状态,我们发现只有五种,即当我们在格子\((i, j)\)时,此时的运动状态为:

  • 方向往上
  • 方向往下
  • 方向往左
  • 方向往右
  • 停下来

因此我们就设\(dp[i][j][0/1/2/3/4/5]\)表示到达格子\((i,j)\),此时状态是上述\(5\)种中的一个,这个情况是否发生(即bool型)

转移就根据当前状态,枚举后继状态转移即可。

因为这里的DP顺序不是常规的循环,而是依赖于当前的运动状态的,所以转移类似于\(BFS\)式的转移。

每个格子最终只会访问到\(5\)次,因此复杂度是\(O(n^2)\)

对于格子\((i, j)\), \(dp[i][j]\)\(5\)个状态中有任意一个状态访问过(为真),则这个格子就访问过了。

答案就是上述这些格子的总和。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int n, m;
    cin >> n >> m;
    vector<string> tu(n);
    for (auto& i : tu)
        cin >> i;
    vector<vector<array<int, 5>>> ok(n,
                                     vector<array<int, 5>>(m, array<int, 5>{}));
    queue<array<int, 3>> team;
    array<int, 4> dx{1, -1, 0, 0};
    array<int, 4> dy{0, 0, 1, -1};
    team.push({1, 1, 2});
    team.push({1, 1, 0});
    ok[1][1][0] = ok[1][1][2] = ok[1][1][4] = 1;
    auto stop = [&](int x, int y) { return tu[x][y] == '#'; };
    while (!team.empty()) {
        auto [x, y, d] = team.front();
        team.pop();
        if (d == 4) { // stop
            for (int i = 0; i < 4; ++i) {
                int nx = x + dx[i], ny = y + dy[i];
                if (!stop(nx, ny) && !ok[nx][ny][i]) {
                    team.push({nx, ny, i});
                    ok[nx][ny][i] = 1;
                }
            }
            continue;
        }
        int nx = x + dx[d], ny = y + dy[d];
        if (stop(nx, ny)) {
            if (!ok[x][y][4]) {
                team.push({x, y, 4});
                ok[x][y][4] = 1;
            }
        } else {
            team.push({nx, ny, d});
            ok[nx][ny][d] = 1;
        }
    };
    int ans = 0;
    for (int i = 0; i < n; ++i)
        for (int j = 0; j < m; ++j) {
            bool access = false;
            for (auto& k : ok[i][j])
                access |= k;
            ans += access;
        }
    cout << ans << '\n';

    return 0;
}



E - Defect-free Squares (abc311 E)

题目大意

给定一个二维网格,有一些格子上有洞。

问有多少个正方形大小的网格,其里面都没洞。

解题思路

考虑一维的情况,我们可以枚举右端点\(r\),然后考虑有多少个左端点\(l\),满足\([l,r]\)都没洞。

容易发现,如果位置\(i\)有洞,那么所有左端点\(l \leq i\)都不满足上述要求。

我们对洞的数量求一个前缀和\(sum\),那么\([l,r]\)没洞等价于\(sum[r] - sum[l - 1] = 0\)

注意到\(sum\)是个单调数组,那么右边的等价式其实是一个关于\(l\)单调函数,我们可以二分枚举\(l\),找到最小的满足\(sum[r] - sum[l - 1] = 0\)的位置,那么右端点为\(r\),满足条件的左端点数量就是\(r - l + 1\)。枚举所有右端点累计求和即为答案。

回到这个二维,我们枚举这个网格的右下角,因为是正方形,我们只需枚举其边长,就能知道左上角的位置,然后题意的条件就相当于一个二维前缀和的相减,同样是一个单调函数,因此问题就解决了,只是一维变二维了。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int h, w, n;
    cin >> h >> w >> n;
    vector<vector<int>> presum(h + 1, vector<int>(w + 1, 0));
    for (int i = 0; i < n; ++i) {
        int u, v;
        cin >> u >> v;
        presum[u][v]++;
    }
    for (int i = 1; i <= h; ++i)
        for (int j = 1; j <= w; ++j) {
            presum[i][j] +=
                presum[i - 1][j] + presum[i][j - 1] - presum[i - 1][j - 1];
        }
    LL ans = 0;
    auto calc = [&](int x, int y) {
        int l = 0, r = min(x, y) + 1;
        while (l + 1 < r) {
            int mid = (l + r) >> 1;
            int lx = x - mid + 1, ly = y - mid + 1;
            int hold = presum[x][y] - presum[lx - 1][y] - presum[x][ly - 1] +
                       presum[lx - 1][ly - 1];
            if (hold == 0) {
                l = mid;
            } else {
                r = mid;
            }
        }
        return l;
    };
    for (int i = 1; i <= h; ++i)
        for (int j = 1; j <= w; ++j) {
            ans += calc(i, j);
        }
    cout << ans << '\n';

    return 0;
}



F - Yet Another Grid Task (abc311 F)

题目大意

给定一个初始二维网格,格子有黑有白。

如果一个格子\((i, j)\)是黑的,那么\((i+1, j),(i + 1, j + 1)\)也必须是黑的。

初始不一定满足上述条件,现在你可以对格子涂成黑色,问满足上述条件的局面数。

解题思路

涂一个点,等价于这个点往下的一个直角三角形都被涂,隐隐约约感觉状态就拆成了这个点涂和下面以及旁边的点要不要涂的状态,但总感觉还要一些额外的状态,比如旁边的点涂不涂貌似会影响到当前决策,但想不出来,寄

首先注意到,如果我们对一个格子涂黑,那么从这个格子开始往下往右的一个直角三角形都要被涂黑。

给定初始局面,先将其变成满足条件的局面,因为一个黑格子仅影响下一行的格子,所以可以逐行遍历,将初始局面染成满足条件的局面\(s\)

然后考虑如何计数,不同的方案就是在上述局面\(s\)的基础上染色的。

考虑按行还是按列枚举计数,写题解的时候细想发现貌似都可以做,以下是按照当时考虑的按列的枚举计数。

当我们一列一列考虑时,我们所采取的决策对这一列的哪一行格子涂成黑色。注意对其涂色的格子必须是白色。

因此为了能够作出决策,在考虑当前列时,我们需要的信息是当前列的格子的颜色,这看似需要一个\(2^n\)的状态,但注意到一次涂色相当于一个直角三角形,这意味着如果该列第\(i\)行被涂成黑色,那么所有\(j \geq i\)行都会被涂成黑色。所以我们需要的状态是该列从哪一行开始被涂成黑色

即设\(dp[i][j]\)表示前\(i\)列,其中第\(i\)列的情况是,\(j\)行及以下都是黑色,以上都是白色,的方案数。

由于涂色是一个三角形,我们只需枚举前一列的格子涂色状态,就能知道其对当前列的格子状态的影响,进而可以在当前列作出决策

考虑如何转移,对于当前第\(i\)行,如果在初始满足条件的局面\(s\)中,第\(j\)行开始是黑色的话,那么所有\(dp[i][k > j]=0\)。因为初始局面就是第\(j\)行及以下都是黑的(以上都是白色)了,又不能对格子涂成白色,因此第\(k > j\)行及以下是黑的格子(以上都是白色)的情况不可能有。

然后考虑\(dp[i][k \leq j]\)的情况,第\(k\)个格子原本是白色,现在涂成黑色(以上都是白色)的情况有两种:

  • 自己涂成黑的,或本身就是黑的
  • 由于前一列的影响,该列的该行是黑的

对于第一种情况,能够累加的前一列的状态就是\(\sum_{j = k}^{n + 1}dp[i - 1][j]\)。(\(dp[i-1][n+1]\)表示该列全部都是白色的格子的情况)

对于第二种情况,对应的前一列的状态是\(dp[i - 1][k - 1]\)

注意\(dp[i-1][j < k - 1]\)的情况会使得第\(i\)\(k-1\)行格子也会涂成黑色,不符合\(dp\)中第二维\(j\)的定义。

因此\(dp[i][k] = \sum_{j = k - 1}^{n + 1}dp[i - 1][j]\),这是一个后缀和,转移的时候预处理就可以\(O(1)\)转移。

初始条件就是\(dp[0][n+1] = 1\)。因为第一列不会受到前一列(不存在的列)的影响,对应的状态就是全白。当然\(dp[0][n] = 1\)也是正确的,它不会影响第一列。

因此总的时间复杂度是\(O(nm)\)

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

const int mo = 998244353;

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int n, m;
    cin >> n >> m;
    vector<string> s(n);
    for (auto& i : s)
        cin >> i;
    for (int i = 0; i < n; ++i)
        for (int j = 0; j < m; ++j) {
            if (s[i][j] == '#') {
                if (i + 1 < n) {
                    s[i + 1][j] = '#';
                    if (j + 1 < m)
                        s[i + 1][j + 1] = '#';
                }
            }
        }
    vector<int> dp(n + 1, 0);
    dp[n] = 1;
    for (int i = 0; i < m; ++i) {
        vector<int> dp2(n + 1, 0);
        int sufsum = 0;
        for (int j = n; j >= 0; --j) {
            sufsum = (sufsum + dp[j]) % mo;
            if (j > 0 && s[j - 1][i] == '#')
                continue;
            if (j > 0) {
                dp2[j] = (dp[j - 1] + sufsum) % mo;
            } else {
                dp2[j] = sufsum;
            }
        }
        dp.swap(dp2);
    }
    int ans = 0;
    for (int i = 0; i <= n; ++i) {
        ans = (ans + dp[i]) % mo;
    }
    cout << ans << '\n';

    return 0;
}


G - One More Grid Task (abc311 G)

题目大意

给定一个二维网格,问一个价值最大的矩形区域。

一个矩形区域的价值为,该区域的数字和$\times $该区域的最小值。

解题思路

考虑一维怎么做。

价值有两项,一个是一个是区域最小值,如果我们枚举左端点,那么右端点变动时,数字和与最小值都可能变化,最坏情况下每移动一个位置,最小值都变一次,感觉避免不了\(O(n^2)\)

我们还可以枚举区域的最小值\(a_i\),那么以该值为最小值的区域的左右端点\(l,r\),可以假想是从\(i\)左右扩张,一直扩张到要小于\(a_i\)位置。这是以它为最小值的最大的区间,因为值都是正的,因此这是一个以该值作为最小值的价值最大的区间。

对每个\(a_i\)都求一遍这个价值取最大就是答案。

而对于一个\(a_i\),如何找到\(l,r\),其实这是一个经典问题,即找左边第一个比它小的数,右边第一个比它小的数,可以运用单调栈\(O(n)\)内找出来。

于是我们事先预处理出每个数的左右边界\(l_i, r_i\),我们枚举\(a_i\)作为最小值,其一个候选答案就是\(a_i \times (sum[r_i] - sum[l_i - 1])\),其中\(sum\)数组也是需要预处理的前缀和数组,用于\(O(1)\)求区间和。答案就是所有候选答案的最大值。此时时间复杂度是\(O(n)\)

回到这个二维,注意规模只有\(300\),如果我们先花\(O(n^2)\)枚举一个上下边界,此时要考虑的就是左右边界了,问题其实就变成了上述的一维情况,一个位置的最小值就是这上下边界中这一列的最小值,而值(用于算前缀和的)就是这一列的数字之和。问题其实解决了。

考虑实现细节,当我们枚举上下边界后,需要求出变成一维情况的数组,其中算左右边界的最小值是该列的最小值,可以预先花\(O(n^2)\)预处理出每一列的两两行的最小值,也可以用ST表。然后算前缀和的是该列的数字之和,因此还得预处理出每一列的前缀和。

总的时间复杂度就是\(O(n^3)\)

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

// usage:
//   auto fun = [&](int i, int j) { return min(i, j); };
//   SparseTable<int, decltype(fun)> st(a, fun);
// or:
//   SparseTable<int> st(a, [&](int i, int j) { return min(i, j); });
template <typename T, class F = function<T(const T&, const T&)>>
class SparseTable {
  public:
    int n;
    vector<vector<T>> mat;
    F func;

    SparseTable(const vector<T>& a, const F& f) : func(f) {
        n = static_cast<int>(a.size());
        int max_log = 32 - __builtin_clz(n);
        mat.resize(max_log);
        mat[0] = a;
        for (int j = 1; j < max_log; j++) {
            mat[j].resize(n - (1 << j) + 1);
            for (int i = 0; i <= n - (1 << j); i++) {
                mat[j][i] = func(mat[j - 1][i], mat[j - 1][i + (1 << (j - 1))]);
            }
        }
    }

    T get(int from, int to) const { // [from, to]
        assert(0 <= from && from <= to && to <= n - 1);
        int lg = 32 - __builtin_clz(to - from + 1) - 1;
        return func(mat[lg][from], mat[lg][to - (1 << lg) + 1]);
    }
};

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int n, m;
    cin >> n >> m;
    vector<vector<int>> a(n, vector<int>(m, 0));
    vector<SparseTable<int>> st;
    vector<vector<int>> presum(n, vector<int>(m, 0));
    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < m; ++j)
            cin >> a[i][j];
        partial_sum(a[i].begin(), a[i].end(), presum[i].begin());
        st.push_back(
            SparseTable<int>(a[i], [](int x, int y) { return min(x, y); }));
    }
    LL ans = 0;
    auto solve = [&](int L, int R) {
        vector<int> num(n), sum(n);
        for (int i = 0; i < n; ++i) {
            num[i] = st[i].get(L, R);
            sum[i] = presum[i][R];
            if (L)
                sum[i] -= presum[i][L - 1];
        }
        partial_sum(sum.begin(), sum.end(), sum.begin());

        vector<int> l(n), r(n);
        stack<int> st;
        for (int i = 0; i < n; ++i) {
            while (!st.empty() && num[st.top()] >= num[i]) {
                r[st.top()] = i - 1;
                st.pop();
            }
            st.push(i);
        }
        while (!st.empty()) {
            r[st.top()] = n - 1;
            st.pop();
        }
        for (int i = n - 1; i >= 0; --i) {
            while (!st.empty() && num[st.top()] > num[i]) {
                l[st.top()] = i + 1;
                st.pop();
            }
            st.push(i);
        }
        while (!st.empty()) {
            l[st.top()] = 0;
            st.pop();
        }

        for (int i = 0; i < n; ++i) {
            int cur = sum[r[i]];
            if (l[i])
                cur -= sum[l[i] - 1];
            ans = max(ans, 1ll * cur * num[i]);
        }
    };
    for (int i = 0; i < m; ++i)
        for (int j = i; j < m; ++j) {
            solve(i, j);
        }
    cout << ans << '\n';

    return 0;
}



Ex - Many Illumination Plans (abc311 Ex)

题目大意

<++>文章来源地址https://www.toymoban.com/news/detail-595492.html

解题思路

<++>

神奇的代码



到了这里,关于AtCoder Beginner Contest 311的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • AtCoder Beginner Contest 310

    感觉F又双叒叕写复杂了 点杯咖啡,要 (p) 元,但可以用一个优惠券,使得咖啡只要 (q) 元,但你需要额外购买 (n) 个商品中(价格为 (a_i) )的一个。 问点杯咖啡的最小价格。 考虑直接买还是使用优惠券,使用优惠券的话就选 (n) 个商品中价格最小的。 两种情况取最小

    2024年02月16日
    浏览(28)
  • AtCoder Beginner Contest 320

    给定 (a,b) ,输出 (a^b + b^a) 。 因为数不超过 (10) ,可以直接用 pow 计算然后转成 (int) 。不会有精度损失。 神奇的代码 给定一个字符串 (s) ,求长度最长的回文串。 因为长度只有 (100) ,可以直接枚举子串,然后暴力判断是不是回文串(翻转后是否和自身相同),复杂

    2024年02月08日
    浏览(38)
  • AtCoder Beginner Contest 348

    给定 (n) ,输出 (ooxooxoox...) ,长度为 (n) 。 按题意模拟即可。 神奇的代码 给定 (n) 个点,对每个点,求出与其距离最远的点的下标。 (n) 只有 (100) ,对于每个点,花 (O(n)) 遍历每个点最大化距离,时间复杂度为 (O(n^2)) 。 神奇的代码 (n) 个豌豆,每个豌豆有美味值

    2024年04月08日
    浏览(42)
  • AtCoder Beginner Contest 329

    劳累一天不该写题,启发式合并都写错了 给定一个字符串,将每个字符输出出来,中间留个空格。 遍历输出即可。 神奇的代码 给定一个数组,找出次大的数。 遍历一下,从非最大的数求一个最大值即可。 神奇的代码 给定一个字符串,问有多少个连续的子串,其由一个字母

    2024年02月05日
    浏览(75)
  • AtCoder Beginner Contest 339

    给一个网址,问它的后缀是多少。 找到最后的\\\'.\\\'的位置,然后输出后面的字符串即可。 python 可以一行。 神奇的代码 二维网格,上下左右相连,左上原点。初始全部为白色,位于原点,面朝上。 进行 (n) 次操作,每次操作,将当前格子颜色黑白反转,然后 如果原来是白色

    2024年02月19日
    浏览(31)
  • AtCoder Beginner Contest 340

    给定等差数列的 首项 、 末项 、 公差 。 输出这个等差数列。 从 首相 依次累加 公差 到 末项 即可。 神奇的代码 依次进行 (Q) 次操作,分两种。 1 x ,将 x 放到数组 (a) 的末尾。 2 k ,输出数组 (a) 的倒数第 (k) 项。 用 vector 模拟即可,操作 2 可快速寻址访问。 神奇的代

    2024年02月19日
    浏览(33)
  • AtCoder Beginner Contest 314

    怎么好多陌生单词 审核怎么这么逆天,半小时还没审完 给定 (pi) 的值以及数 (n) ,要求保留 (n) 位小数输出,不四舍五入。 字符串形式储存然后截取末尾即可。 神奇的代码 (n) 个人玩轮盘赌游戏,简单说就是一个转盘有 (37) 个数字以及一个箭头,箭头会等概率停在某

    2024年02月13日
    浏览(40)
  • AtCoder Beginner Contest 308

    这几天在收拾东西搬家,先附上代码,晚点补上题解 补完了 感觉这次FG都写不太明白 给定八个数,问是否满足以下要求: 不严格升序 每个数在 (100 sim 675) 之间 每个数都是 (25) 的倍数 依次对每个数判断是否符合这三个条件即可。 神奇的代码 高桥吃 (n) 份寿司,寿司分

    2024年02月11日
    浏览(30)
  • AtCoder Beginner Contest 345

    给定一个字符串,问是不是形如 ======...==== 的字符串。 根据长度构造出期望的字符串,再判断是否相等即可。 神奇的代码 给定 (a) ,输出 (lceil frac{a}{10} rceil) 上下取整的转换, (lceil frac{a}{10} rceil = lfloor frac{a + 9}{10} rfloor) 。用下取整即可。 Python 的 // 是下取证,

    2024年03月21日
    浏览(36)
  • AtCoder Beginner Contest 309

    感觉F写了个乱搞做法 给定一个 (3 times 3) 的网格,以及两个数字。 问这两个数字是否 水平相邻 。 求出两个数字的横纵坐标,看是否横坐标相同,纵坐标差一即可。 读题不仔细,开题就WA了。 神奇的代码 给定一个矩形。将最外围的数字顺时针旋转一格。 可以模拟一个指针

    2024年02月13日
    浏览(32)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包