AtCoder Beginner Contest 307

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

A - Weekly Records (abc307 A)

题目大意

给定\(n\)周每天的散步量,求每周七天的散步量的和。

解题思路

累计求和即可。

神奇的代码
#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;
    while(n--){
        int sum = 0;
        for(int i = 0; i < 7; ++ i){
            int a;
            cin >> a;
            sum += a;
        }
        cout << sum << ' ';
    }

    return 0;
}



B - racecar (abc307 B)

题目大意

给定\(n\)个字符串 \(s\),问能否选择两个 \(i,j\),满足 \(i \neq j\),且 \(s_i + s_j\)是个回文串。

解题思路

只有\(100\)个串,直接 \(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;
    cin >> n;
    vector<string> s(n);
    for(auto &i : s)
        cin >> i;
    auto palind = [&](int x, int y){
        string l = s[x] + s[y];
        string r = l;
        reverse(r.begin(), r.end());
        return l == r;
    };
    auto ok = [&](){
        for(int i = 0; i < n; ++ i)
            for(int j = 0; j < n; ++ j){
                if (i != j && palind(i, j))
                    return true;
            }
        return false;
    };
    if (ok())
        cout << "Yes" << '\n';
    else 
        cout << "No" << '\n';

    return 0;
}



C - Ideal Sheet (abc307 C)

题目大意

给定两个二维网格的模式图,有一些格子是的,有一些格子是透明的。

问能否通过这两个模式图得到一个期望的模式图,要求格子的属性(透明)相同。

操作是将这两个模式图分别盖印到一个无穷大的二维网格上,仅能盖一次,然后通过裁剪得到期望模式图。

注意盖印的黑色格子都必须保留,不得裁剪掉。

解题思路

因为模式图大小只有\(10 \times 10\),因此直接 \(O(10^4)\)枚举两个模式图的盖印位置,然后判断盖印后的结果是否与期望模式图相同。

注意盖印后的黑色格子都必须在期望模式图范围内,所以还需统计期望模式图范围内的两个模式图的黑色格子数,不能有超出期望模式图范围的黑色格子。

神奇的代码
#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);
    array<int, 3> h, w;
    array<vector<string>, 3> d;
    array<int, 3> blk{};

    for(int i = 0; i < 3; ++ i){
        cin >> h[i] >> w[i];
        d[i].resize(h[i]);
        for(int j = 0; j < h[i]; ++ j){
            cin >> d[i][j];
            blk[i] += count(d[i][j].begin(), d[i][j].end(), '#');
        }
    }

    auto check = [&](int x1, int y1, int x2, int y2){
        int tot = 0;
        for(int i = 0; i < h[2]; ++ i)
            for(int j = 0; j < w[2]; ++ j){
                int target = (d[2][i][j] == '#');
                int blk1 = (i >= x1 && j >= y1  && i - x1 < h[0] && j - y1 < w[0] && d[0][i - x1][j - y1] == '#');
                int blk2 = (i >= x2 && j >= y2 && i - x2 < h[1] && j - y2 < w[1] && d[1][i - x2][j - y2] == '#');
                tot += blk1 + blk2;
                if (target != (blk1 || blk2))
                    return false;
            }
        return (tot == blk[0] + blk[1]);
    };

    auto ok = [&](){
        for(int i = -10; i <= 10; ++ i)
            for(int j = -10; j <= 10; ++ j)
                for(int k = -10; k <= 10; ++ k)
                    for(int l = -10; l <= 10; ++ l){
                        if (check(i, j, k, l))
                            return true;
                    }
        return false;
    };

    if (ok())
        cout << "Yes" << '\n';
    else 
        cout << "No" << '\n';

    return 0;
}



D - Mismatched Parentheses (abc307 D)

题目大意

给定一个包含小写字母、()的字符串。每次选择一个开头是(,结尾是),且中间不包含这两个字符的子串,移除该子串。

问最终剩余的字符串是怎样的。

解题思路

每次移除的其实是一个最里面的合法的(),假设有(()()),每次移除的都是最里端的一对(),因此反复这么操作,合法的括号序列都会被移除掉。

因此我们对原字符串的每个(统计与之对应的),这样每个()之间的内容都会被移除。

根据这个对应关系就会跳过一些字符串(被移除了),剩下的就是没被移除的了。

神奇的代码
#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;
    vector<int> r(n, -1);
    stack<int> pos;
    for(int i = 0; i < n; ++ i){
        if (s[i] == '(')
            pos.push(i);
        else if (s[i] == ')' && !pos.empty()){
            r[pos.top()] = i;
            pos.pop();
        }
    }
    for(int i = 0; i < n; ++ i){
        if (r[i] != -1){
            i = r[i];
        }else {
            cout << s[i];
        }
    }
    cout << '\n';

    return 0;
}



E - Distinct Adjacent (abc307 E)

题目大意

给定一个\(n\)个数的环形数组,每个数的取值为\([1, m]\)

问有多少种取值情况,满足任意相邻两个数不相同。

解题思路

如果不是环形,答案很明显是\(m \times (m - 1)^(n - 1)\)。但由于环形,最后一位的取值难以确定是\(n - 1\)还是 \(n - 2\),这取决于倒数第二位是否和第一位相同。

如何解决呢?其实问题的核心上面已经指明了:倒数第二位是否和第一位相同

  • 如果相同,则最后一位可取\(n - 1\)种情况。
  • 如果不相同,则最后一位可取 \(n - 2\)种情况。

由于每个数都是对称的,我们假设第一位填的数是 \(1\)。然后设 \(dp[i][0/1]\)表示前 \(i\)位,相邻两数不同(不考虑首尾),且最后一位与首位数字不同/相同的方案数。转移就看当前位是否与首位相同。

因为上述我们假定了首位取\(1\),事实上首位取什么值情况都一样,而它有 \(m\)种取法,因此最终答案就是\(m \times dp[n][0]\)

神奇的代码
#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;
    array<int, 2> dp{0, 1};
    for(int i = 1; i < n; ++ i){
        array<int, 2> dp2{};
        dp2[0] = 1ll * dp[0] * (m - 2) % mo + 1ll * dp[1] * (m - 1) % mo;
        if (dp2[0] >= mo)
            dp2[0] -= mo;
        dp2[1] = dp[0];
        dp.swap(dp2);
    }
    cout << 1ll * dp[0] * m % mo << '\n';

    return 0;
}



F - Virus 2 (abc307 F)

题目大意

给定一张无向图,边有边权。

初始有\(a\)个点感染病毒。

持续 \(d\)天。对于第 \(i\)天,所有与已感染病毒的点距离不超过\(x_i\)的点都会被感染病毒。

问每个点第一次被感染病毒的天数。

解题思路

因为有多个起点,我们建立一个超级源点\(s\),它与所有已感染病毒的点连一条边权为 \(0\)的无向边。

考虑第一天,我们从超级源点 \(s\)开始 \(dijkstra\),所有距离不超过 \(x\)的点都会被感染病毒。然后新感染的点又会与超级源点\(s\)连一条边权为 \(0\)的边。很显然当跑到距离大于\(x\)时我们就无需继续跑 \(dijkstra\)了。

之后第二天重复跑\(dijkstra\),依次类推,时间复杂度是 \(O(dn\logm)\),是无法通过的。

究其原因,主要是舍弃了之前信息,重复操作了:当一个新的点\(u\)被感染时,它会影响与其相邻点\(v\)的最短路距离,这个相邻点只包含未被感染的点——因为已被感染的点\(v\)意味着与超级源点\(s\)有条 \(0\)边权的边,从\(v\)出发的最短路一定优于从\(u \to v\)的。因此对于已被感染的点未被感染的点的边的情况是可以继承上一次\(dijkstra\),即复用前一天的\(dijkstra\)的优先队列,而不用重新求,因为没有变化。

换句话说,假设第一天有\(k\)个新感染的点 \(u_i\),我们只需要更新这些新感染的点未被感染的相邻点的最短距离,将其加入到 \(dijkstra\)的优先队列里。第二天就继续从这个优先队列开始\(dijkstra\)

这样,每条边最多只会被考虑两次——一次是正常的 \(dijkstra\),另一次是当其中一个端点被感染病毒后重新考虑。

因此总的时间复杂度是 \(O(n\log m)\)

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

const LL inf = 1e18;

int main(void) {
    ios::sync_with_stdio(false); 
    cin.tie(0); cout.tie(0);
    int n, m;
    cin >> n >> m;
    vector<vector<array<int, 2>>> edge(n);
    for(int i = 0; i < m; ++ i){
        int u, v, w;
        cin >> u >> v >> w;
        -- u, -- v;
        edge[u].push_back({v, w});
        edge[v].push_back({u, w});
    }
    vector<LL> dis(n, inf);
    vector<int> ans(n, -1);
    priority_queue<pair<LL, int>> team;
    int k;
    cin >> k;
    for(int i = 0; i < k; ++ i){
        int u;
        cin >> u;
        -- u;
        dis[u] = 0;
        ans[u] = 0;
        for(auto &[v, w] : edge[u]){
            if (dis[v] > dis[u] + w){
                dis[v] = dis[u] + w;
                team.push({-dis[v], v});
            }
        }
    }
    int d;
    cin >> d;
    for(int i = 0; i < d; ++ i){
        int x;
        cin >> x;
        vector<int> infected;
        while(!team.empty()){
            auto [d, u] = team.top();
            if (dis[u] > x)
                break;
            team.pop();
            if (dis[u] != -d || ans[u] != -1)
                continue;
            ans[u] = i + 1;
            infected.push_back(u);
            for(auto &[v, w] : edge[u]){
                if (dis[v] > dis[u] + w){
                    dis[v] = dis[u] + w;
                    team.push({-dis[v], v});
                }
            }
        }
        for(auto &u : infected)
            for(auto &[v, w]: edge[u]){
                if (dis[v] > w){
                    dis[v] = w;
                    team.push({-dis[v], v});
                }
            }
    }
    for(int i = 0; i < n; ++ i)
        cout << ans[i] << "\n";

    return 0;
}



G - Approximate Equalization (abc307 G)

题目大意

给定一个有\(n\)个数的数组,可进行两种操作:

  • 选定一个\(i\),令 \(a_i = a_i - 1, a_{i + 1} = a_{i + 1} + 1\)
  • 选定一个\(i\),令 \(a_i = a_i + 1, a_{i + 1} = a_{i + 1} - 1\)

问最少进行的操作次数,使得数组的极差不超过\(1\)

解题思路

观察操作,可以发现每次操作后,这个数组的总和\(sum\)是不变的。(其实可以看成有\(sum\)个小球, \(n - 1\)个隔板,每次操作其实就是移动一个隔板到相邻位置)

由于极差不超过\(1\),那么最终每个数要么是 \(div = \lfloor \frac{sum}{n} \rfloor\),要么是 \(div + 1\),且至多有\(sum \% n\)个数是后者。问题就是让哪些数是后者,哪些数是前者。

首先明确一点,如果给定最终的数组,求将该数组变成最终数组的操作次数,其最优方案是可以在\(O(n)\)内求出来。就是依次对每个数\(a_i\),将其变为目标值\(t_i\),需要从后一个数 \(a_{i + 1}\)多少次(操作二),还是多少次(操作一),然后这个的影响是持续的。

现在问题变成了让哪些数是\(div\),哪些是\(div+1\),这是个分配问题,设 \(dp[i][j]\)表示前 \(i\)个数,还有 \(j\)\(div+1\)的未分配的最小操作数。需要第二维状态一方面是要确保有\(mod\)\(div + 1\),另一方面是需要知道由于前面的数的操作,当前数变成了多少。

对于当前数\(a_i\),知道了 \(j\),我们就知道前面有多少个数是 \(div+1\),多少个数是\(div\),进而知道,在上述求解操作的影响下, \(a_i\)变成了多少,然后就可以进一步求解 \(a_i\)变成目标数所需要的操作次数了。

注意如果\(mod = sum \% n\)是负数,则可以让\(div = div - 1, mod = n - mod\),这样就变回正数了。

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

const LL inf = 1e18;

int main(void) {
    ios::sync_with_stdio(false); 
    cin.tie(0); cout.tie(0);
    int n;
    cin >> n;
    vector<LL> a(n);
    for(auto &i : a)
        cin >> i;
    LL sum = accumulate(a.begin(), a.end(), 0ll);
    LL div = sum / n, mod = sum % n;
    if (mod < 0){
        mod = n + mod;
        div --;
    }
    vector<LL> dp(mod + 1, inf);
    dp[mod] = 0;
    LL presum = 0;
    LL tot = 0;
    for(auto &x : a){
        vector<LL> dp2(mod + 1, inf);
        for(int i = 0; i <= mod; ++ i){
            LL cur = x - (tot + mod - i - presum);
            dp2[i] = min(dp2[i], dp[i] + abs(cur - div));
            if (i)
                dp2[i - 1] = min(dp2[i - 1], dp[i] + abs(cur - div - 1));
        }
        presum += x;
        tot += div;
        dp.swap(dp2);
    }
    cout << dp[0] << '\n';

    return 0;
}



Ex - Marquee (abc307 Ex)

题目大意

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

解题思路

<++>

神奇的代码



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

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

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

相关文章

  • AtCoder Beginner Contest 327

    2023.11.08 update: 更新了G 给定一个字符串 (s) ,问是否包含 ab 或 ba 。 遍历判断即可。 神奇的代码 给定 (b) ,问是否存在 (a) 使得 (a^a = b) 。 由于指数爆炸的缘故, (a) 的范围不会很大,枚举 (a) ,看 (b % a) 是否为 (0) ,然后用 (b) 不断除以 (a) ,看最后除的次数是

    2024年02月06日
    浏览(45)
  • AtCoder Beginner Contest 331

    给定一年的月数和一月的天数,以及当天日期,问次日的日期。 一个简单的进制加法运算,超出进制数则向前加一。 神奇的代码 给定 (6,8,12) 根胡萝卜的价格。 问买至少 (n) 根胡萝卜的最小花费。 由于 (n) 只有 (100) ,花 (O(n^3)) 枚举这三类胡萝卜的购买次数,取花费最

    2024年02月05日
    浏览(35)
  • AtCoder Beginner Contest 301

    给定一个字符串表示高桥和青木每局的获胜情况。 如果高桥获胜局数多,或者两个胜局相等,但高桥率先取得那么多胜场,则高桥获胜,否则青木获胜。 问谁获胜。 按照题意,统计两者的获胜局数比较即可。 如果两者局数相等,可以看最后一局谁胜,青木胜则意味着高桥率

    2024年02月04日
    浏览(52)
  • AtCoder Beginner Contest 341

    给定 (n) ,输出 (n) 个 (0) 和 (n+1) 个 (1) 交替的字符串。 (101010...) 循环输出即可。 神奇的代码 货币兑换。 (A) 国货币每 (x_a) 钱可兑换 (B) 国货币 (y_a) 钱。 (B) 国货币每 (x_b) 钱可兑换 (C) 国货币 (y_b) 钱。 ... 给定你拥有的每国货币钱数和兑换规则,依次兑换

    2024年02月19日
    浏览(26)
  • AtCoder Beginner Contest 318

    咕咕咕,总力战还没打,凹不过卷狗,躺了.jpg 给定 (n, m, p) ,问有多少个 (i) 满足 (0 m+pi leq n) 减去初始的 (m) ,剩下的就是看 (p) 的倍数个数。 神奇的代码 一个二维空间,有 (n) 个矩形覆盖。 问有多大的空间被覆盖了。重复覆盖算一次。 空间大小只有 (100) ,矩形

    2024年02月10日
    浏览(39)
  • AtCoder Beginner Contest 337

    给定 (n) 场比赛高桥和青木的得分。 问最后是总分,是高桥高还是青木高,还是打平了。 累计比较大小即可。 神奇的代码 给定一个字符串,问是否形如 AAAA..BBBB...CCCC 。 如果形如上述,那么通过 unique 函数去重后就是有序的,因此去重排序比较即可。 当然也可以朴素找出

    2024年01月21日
    浏览(34)
  • AtCoder Beginner Contest 324

    在高铁上加训! 给定 (n) 个数,问是否都相等。 判断是不是全部数属于第一个数即可。或者直接拿 set 去重。 神奇的代码 给定一个数 (N) ,问是否能表示成 (2^x3^y) 。 指数范围不会超过 (64) ,花 (O(64^2)) 枚举判断即可。 神奇的代码 给定一个字符串 (t) 和若干个字符串

    2024年02月08日
    浏览(34)
  • AtCoder Beginner Contest 329

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

    2024年02月05日
    浏览(74)
  • AtCoder Beginner Contest 320

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

    2024年02月08日
    浏览(35)
  • AtCoder Beginner Contest 300

    给定一个元素互不相同的数组 (c) 和 (a,b) ,找到 (i) 使得 (c_i = a + b) 直接 for 循环寻找即可。 神奇的代码 给定两个矩阵 (A,B) ,问能否对 (A) 进行若干次变换操作得到 (B) 。 变换分两种,一种是将第一列放到最后一列,另一种是将第一行放到最后一行。 范围不大,直

    2024年02月01日
    浏览(51)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包