AtCoder Beginner Contest 337

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

A - Scoreboard (abc337 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;
    LL a = 0, b = 0;
    while (n--) {
        int x, y;
        cin >> x >> y;
        a += x;
        b += y;
    }
    if (a > b)
        cout << "Takahashi" << '\n';
    else if (a < b)
        cout << "Aoki" << '\n';
    else
        cout << "Draw" << '\n';

    return 0;
}



B - Extended ABC (abc337 B)

题目大意

给定一个字符串,问是否形如AAAA..BBBB...CCCC

解题思路

如果形如上述,那么通过unique函数去重后就是有序的,因此去重排序比较即可。

当然也可以朴素找出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);
    string s;
    cin >> s;
    s.erase(unique(s.begin(), s.end()), s.end());
    auto t = s;
    ranges::sort(t);
    if (s == t)
        cout << "Yes" << '\n';
    else
        cout << "No" << '\n';

    return 0;
}



C - Lining Up 2 (abc337 C)

题目大意

\(n\)个人站成一排,给定每个人位于某人的右边,还原这个排列。

解题思路

对上述信息作一个映射\(r[i]=j\),即第 \(j\)个人在第 \(i\)个人的右边。

然后找到最左边的人\(l\), 不断\(l=r[l]\)即可还原这个排列。

神奇的代码
#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;
    map<int, int> pos;
    for (int i = 0; i < n; i++) {
        int x;
        cin >> x;
        pos[x] = i + 1;
    }
    int st = pos[-1];
    cout << st;
    for (int i = 0; i < n - 1; i++) {
        st = pos[st];
        cout << ' ' << st;
    }
    cout << '\n';

    return 0;
}



D - Cheating Gomoku Narabe (abc337 D)

题目大意

给定\(h \times w\)的二维网格,格子上有 xo.三种之一。

问最少进行的操作数,使得网格有连续\(k\)(水平或垂直)个格子都是 o

操作为:将一个为.的格子改为o

解题思路

\(h\times w\)只有 \(2\times 10^5\),因此可以枚举这连续\(k\)个格子的左端点 ,然后往右的\(k\)个格子里,不能有 x,然后对.的数量取个最小值。事先预处理一下关于x.数量的前缀和,即可\(O(1)\)判断和求答案。当然也可以滑动窗口直接维护这两字符的数量。

水平判一次后,将原图旋转 \(90\)度再判一次即可。时间复杂度是\(O(hw)\)

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

const int inf = 1e9;

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int h, w, k;
    cin >> h >> w >> k;
    vector<string> s(h);
    for (auto& i : s)
        cin >> i;
    vector<string> t(w);
    for (int i = 0; i < w; i++) {
        for (int j = 0; j < h; j++) {
            t[i] += s[j][i];
        }
    }
    auto solve = [&](vector<string>& s) {
        int ans = inf;
        for (auto& i : s) {
            vector<int> preo(i.size(), 0), prex(i.size(), 0);
            preo[0] = i[0] == 'o';
            prex[0] = i[0] == 'x';
            for (int j = 1; j < i.size(); ++j) {
                preo[j] = preo[j - 1] + (i[j] == 'o');
                prex[j] = prex[j - 1] + (i[j] == 'x');
            }
            for (int j = 0; j + k - 1 < i.size(); ++j) {
                if (prex[j + k - 1] - (j ? prex[j - 1] : 0) == 0) {
                    ans =
                        min(ans, k - (preo[j + k - 1] - (j ? preo[j - 1] : 0)));
                }
            }
        }
        return ans;
    };
    int ans = min(solve(s), solve(t));
    if (ans == inf)
        ans = -1;
    cout << ans << '\n';

    return 0;
}



E - Bad Juice (abc337 E)

题目大意

交互题。

\(n\)个果汁,一个是坏的,喝了第二天会拉肚子。现在你要找出这个坏果汁是哪个。

你需要找最少数量的朋友,然后给他们一些果汁喝。一瓶果汁可以给多个朋友喝,一个朋友也可以喝多个果汁。

如果有朋友喝到坏的果汁,则第二天会告诉你他肚子疼。

你需要根据朋友第二天是否肚子疼的情况判断是哪个果汁坏了。

解题思路

一个经典的判断膺品的题。

考虑对果汁编号\(0 \sim n - 1\),考虑其二进制。对于坏的果汁\(x\),我们需要确定\(x\)的二进制下,哪些数位是 \(1\)

对于第 \(i\)个朋友(编号从 \(0\)开始),它的作用就是判断\(x\)的第\(i\)位是否为\(1\)。即把 \(0 \sim n - 1\)中,所有二进制下第 \(i\)位为 \(1\)的都给第 \(i\)个朋友喝,如果它第二天肚子疼了,说明 \(x\)的第 \(i\)位为 \(1\)

最终需要 \(\lceil \log_2 n \rceil\)个朋友。

神奇的代码
#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;
    int num = 0;
    while ((1 << num) < n)
        num++;
    cout << num << '\n';
    for (int i = 0; i < num; ++i) {
        vector<int> candi;
        for (int j = 0; j < n; ++j) {
            if ((j >> i) & 1)
                candi.push_back(j);
        }
        cout << candi.size() << ' ';
        for (auto x : candi)
            cout << x + 1 << ' ';
        cout << '\n';
    }
    cout.flush();
    string s;
    cin >> s;
    int ans = 0;
    for (int i = 0; i < s.size(); ++i) {
        if (s[i] == '1')
            ans += (1 << i);
    }
    cout << ans + 1 << endl;

    return 0;
}



F - Usual Color Ball Problems (abc337 F)

题目大意

给定\(n\)个球的颜色\(c_i\)\(m\)个盒子。

回答 \(n\)个询问。

\(i\)个(从\(0\)开始)询问 ,将前\(i\)个球放到最后。

然后依次考虑每个球\(c_i\)

  • 若有已经放了\(c_i\)个球的盒子,且数量不超过 \(k\),则放入
  • 否则,若有空盒子,则放入空盒子标号中,多个空盒子就放到标号最小的那个。
  • 否则,吃了它。

问放入盒子的球的数量。

解题思路

先对第\(0\)个询问模拟求出答案。

注意到转移到下一个询问时,其实只有一个盒子的球会变动,然后感觉暴力模拟下就好了,但感觉写不明白一觉醒来感觉明白了。

首先整成\(2n\)个球,后面 \(n\)个球的颜色与前面相同,这样对于第\(i\)个询问,就考虑区间\([i,i+n]\)的球。

然后预处理出\(col[i]\)表示颜色为 \(i\)的球的下标数组。注意到某个颜色被放入盒子的话,它始终是 \(col[i]\)的一个前缀(当然是从当前位置\(i\)开始)。因此我们再维护\(l[i],r[i]\) 表示\(col[i]\)中下标在\([l[i], r[i])\)的球被放入了盒子里。很显然答案\(ans=\sum_{i=1}^{n} r[i] - l[i]\)

考虑我们求得了第 \(i - 1\)个询问的答案,考虑如何求第 \(i\)个询问 ,即如何更新\(l\)数组和 \(r\)数组。

考虑此时的第一个球的颜色\(color\),它将要放到最后面,这会带来什么影响。

  • 首先,该球原来肯定放到了某个盒子里,因为它要被放到后面,所以这个盒子空了,首先\(l[color] += 1. r[color] += 1\)
  • 然后再减去该盒子对该颜色球的影响,此时放入盒子的颜色\(color\)的球会减少,思考减少多少。
  • 假设该颜色球放入盒子数为\(x\)个,实际上会减少 \(x % k + k \times [x % k == 0]\) ,即会减少\(x % k\)个,如果 \(x % k == 0\)则减少 \(k\)个,即该颜色球的最后一个盒子(数量最少)的数量。
  • 考虑这个盒子将会被什么颜色球占领。很显然将会是取得\(\min(col[i][r[i]])\)对应的颜色,取对应数量的颜色球放入即可,因此还得开一个set维护\(col[i][r[i]]\)的最小值。

注意更新\(l,r\)数组时要同步更新 $ans和set

按上述模拟,总时间复杂度是\(O(n\log 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, m, k;
    cin >> n >> m >> k;
    vector<int> c(2 * n);
    for (int i = 0; i < n; ++i) {
        cin >> c[i];
        c[i]--;
        c[i + n] = c[i];
    }
    vector<int> l(n, 0), r(n, 0);
    vector<vector<int>> col(n);
    set<int> minn;
    int ans = 0;
    for (int i = 0; i < 2 * n; ++i) {
        col[c[i]].push_back(i);
    }
    for (int i = 0; i < n; ++i) {
        if (!col[i].empty()) {
            minn.insert(col[i][r[i]]);
        }
    }
    auto modify_r = [&](int color, int R) {
        if (r[color] < col[color].size())
            minn.erase(col[color][r[color]]);
        ans -= r[color] - l[color];
        r[color] = R;
        ans += r[color] - l[color];
        if (r[color] < col[color].size())
            minn.insert(col[color][r[color]]);
    };

    auto push_box = [&](int pos, int R) {
        int color = c[pos];
        int r1 = min((int)col[color].size(), r[color] + k);
        int r2 = ranges::lower_bound(col[color], R) - col[color].begin();

        modify_r(color, min(r1, r2));
    };
    auto pop_box = [&](int pos, int L, int R) {
        int color = c[pos];
        int cnt = (r[color] - l[color]) % k;
        if (cnt == 0)
            cnt = k;

        l[color]++;
        ans--;
        modify_r(color, r[color] + 1);
        modify_r(color, r[color] - cnt);
    };
    auto not_in_box = [&](int pos) {
        int color = c[pos];
        return not(col[color][l[color]] <= pos and pos < col[color][r[color]]);
    };
    for (int i = 0; i < n; ++i) {
        if (not_in_box(i) && m) {
            push_box(i, n);
            --m;
        }
    }
    cout << ans << '\n';

    for (int i = 0; i < n - 1; ++i) {
        pop_box(i, i, i + n);
        if (!minn.empty()) {
            int pos = *minn.begin();
            push_box(pos, i + 1 + n);
        }

        cout << ans << '\n';
    }

    return 0;
}



G - Tree Inversion (abc337 G)

题目大意

给定一棵树,对每个点\(u\),求 \(f(u)\),表示为 \(v\)的数量,使得 \(u \to v\)的路径上有点 \(w > v\)

解题思路

这里涉及到三个点\(u,v,w\),依次考虑枚举哪个,发现枚举 \(u,v\)不会求,而枚举 \(w\)能求。

考虑枚举 \(w\),其有 \(x\)个儿子和一个父亲。然后考虑 \(v\)在哪里:

  • \(v\)\(w\)的一个儿子\(son\)子树,里面点标号小于 \(w\)的都是 \(v\),假设数量有 \(y\)个,那么 \(w\)的其他分支(包括 \(w\))的所有点 \(u\)\(f(u)+=y\)。但这个其他分支的加法不太好做,我们可以维护全局加法\(tot+=y\),然后 这个儿子\(son\)子树的所有点\(u\)\(f(u)-=y\),这样就可以了。
  • \(v\)\(w\)的父亲分支那部分,假设有 \(y\)个,那 \(w\)的所有儿子(包括 \(w\))的所有点 \(u\)\(f(u) += y\),这是一个子树的加法,直接加就好了。

当然这里的子树加法不是子树的每个点都\(f(u)+=y\),注意到这里就多次修改,一次查询,因此树上差分就好了,或者说对要操作的子树的根打个标记,最后来一次\(DFS\)求答案时再传递标记即可。

剩下的问题就是如何求\(y\),怎么求\(w\)的一个儿子\(son\)子树中,标号小于\(w\)的数量。

代码里写的比较复杂(不过板子是贴的),用的动态开点的权值线段树+线段树合并。求一个数在一堆区间的排名,一般可以转换成一个权值线段树的前缀和。由于在DFS时,每个点都要开一棵线段树,普通线段树会爆空间,但由于点权值是稀疏的,得用动态开点线段树。然后DFS合并两个儿子的权值线段树时,还得线段树合并。

\(v\)\(w\)父亲部分时求 \(y\)的数量,可以转换成求 \(w\)所有儿子中小于 \(w\)的数量\(z\), 这样\(y = w - 1 - z\)。就和第一种情况统一了。


如何求\(y\),还有个更简单的办法,先对树求一个\(DFS\)序,与此同时还有一个初始为\(0\)的数组\(val\)。然后我们按照点编号从小到大依次考虑,假设当前考虑点\(u\) ,让\(val[time[u]]++\) ,即点\(u\)所在的 \(DFS\)序上的位置 \(+1\)。然后要求\(u\)的一个儿子 \(son\)的子树中标号小于 \(u\)的数量,则为这个子树对应的一个 \(DFS\)序的 \(val\)的区间和(因为 \(1 \sim u\)都加进来了)。

此处为单点修改和区间查询,用线段树或树状数组维护这个 \(val\)即可。文章来源地址https://www.toymoban.com/news/detail-809978.html

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

const int maxn = 2e5 + 7;

int n;
int rt[maxn], ls[maxn * 20], rs[maxn * 20], num[maxn * 20], cnt;
inline void pushup(int x) { 
    num[x] = num[ls[x]] + num[rs[x]];
}
void update(int& x, int l, int r, int pos, int val = 1) {
    if (!x)
        x = ++cnt;
    if (l == r) {
        num[x] += val;
        return;
    }
    int mid = (l + r) >> 1;
    if (pos <= mid)
        update(ls[x], l, mid, pos, val);
    else
        update(rs[x], mid + 1, r, pos, val);
    pushup(x);
}
int merge(int x1, int x2, int l, int r) {
    if ((!x1) || (!x2))
        return x1 + x2;
    if (l == r) {
        num[x1] += num[x2];
        return x1;
    }
    int mid = (l + r) >> 1;
    ls[x1] = merge(ls[x1], ls[x2], l, mid);
    rs[x1] = merge(rs[x1], rs[x2], mid + 1, r);
    pushup(x1);
    return x1;
}
int query(int x, int l, int r, int L, int R) {
    if (!x)
        return 0;
    if (L <= l && r <= R)
        return num[x];
    int mid = (l + r) >> 1, ans = 0;
    if (L <= mid)
        ans += query(ls[x], l, mid, L, R);
    if (R > mid)
        ans += query(rs[x], mid + 1, r, L, R);
    return ans;
}

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin >> n;
    vector<vector<int>> edge(n);
    for (int i = 0; i < n - 1; ++i) {
        int u, v;
        cin >> u >> v;
        --u, --v;
        edge[u].push_back(v);
        edge[v].push_back(u);
    }
    LL tot = 0;
    vector<LL> p(n, 0);
    auto DFS = [&](auto self, int u, int fa) -> void {
        for (auto& v : edge[u]) {
            if (v == fa)
                continue;
            self(self, v, u);
            LL tmp = query(rt[v], 1, n, 1, u + 1);
            tot += tmp;
            p[v] -= tmp;
            rt[u] = merge(rt[u], rt[v], 1, n);
        }
        update(rt[u], 1, n, u + 1);
        p[u] += u + 1 - query(rt[u], 1, n, 1, u + 1);
    };
    DFS(DFS, 0, 0);
    vector<LL> ans(n, 0);
    auto solve = [&](auto self, int u, int fa, LL pre) -> void {
        ans[u] += tot + p[u] + pre;
        for (auto& v : edge[u]) {
            if (v == fa)
                continue;
            self(self, v, u, pre + p[u]);
        }
    };
    solve(solve, 0, 0, 0);
    for (auto& i : ans)
        cout << i << ' ';
    cout << '\n';

    return 0;
}



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

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

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

相关文章

  • AtCoder Beginner Contest 314

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

    2024年02月13日
    浏览(36)
  • AtCoder Beginner Contest 326

    100楼层,一次可以上最多两层,或下最多三层。 给定两楼层,问能否一次到达。 比较大小,然后判断其差是是不是在 (2) 或 (3) 内即可。 神奇的代码 给定一个 (n) ,问不小于 (n) 的形如 (326) 的数字是多少。 形如 (326) 的数字,即数位有 (3) ,且百位 (times) 十位

    2024年02月08日
    浏览(33)
  • AtCoder Beginner Contest 344

    给定一个字符串,包含两个 | ,将 | 和两个 | 之间的字符消去。 按照题意模拟即可。 Python 比较简洁。 神奇的代码 给定 (n) 个数,倒序输出。 储存这 (n) 个数,然后倒着输出即可。 神奇的代码 给定三个数组 (a,b,c) ,回答 (q) 次询问。 每次询问,给定 (x) ,问能否从三

    2024年03月10日
    浏览(50)
  • AtCoder Beginner Contest 302

    给定怪物的血量 (a) 和你每次攻击扣除的血量 (b) ,问打多少次怪物才会死。 答案即为 (lceil frac{a}{b} rceil = lfloor frac{a + b - 1}{b} rfloor) 神奇的代码 给定一个二维网格,格子上有字母,找到一连串位置,使得其字符串为 (snuke) ,要求这一连串位置俩俩相邻,即有共边或

    2024年02月05日
    浏览(69)
  • AtCoder Beginner Contest 305

    给定一个数字 (x) ,输出一个数字,它是最接近 (x) 的 (5) 的倍数。 令 (y = x % 5) ,如果 (y leq 2) ,那答案就是 (x - y) ,否则就是 (x + 5 - y) 。 神奇的代码 给定 (ABCDEFG) 的相邻距离,给定两个字母,问它们的距离。 累加距离即可。 神奇的代码 二维平面,有一个矩形

    2024年02月08日
    浏览(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日
    浏览(25)
  • AtCoder Beginner Contest 328

    给定 (n) 个数字和一个数 (x) 。 问不大于 (x) 的数的和。 按找要求累计符合条件的数的和即可。 神奇的代码 给定一年的月数和一个月的天数。 问有多少对 ((i,j)) ,表示第 (i) 个月的第 (j) 日, (i,j) 的数位上每个数字都是一样的。 范围只有 (O(100^2)) ,枚举所有的

    2024年02月05日
    浏览(34)
  • AtCoder Beginner Contest 330

    给定 (n) 个学生的分数,以及及格分 (x) ,问多少人及格了。 依次判断即可。 神奇的代码 回答 (n) 个问题,每个问题给定 (a,l,r) ,问函数 (f(x) = |x - a|) 在 ([l,r]) 的最小值。 全定义域下,最小值显然在 (x=a) 取得。绝对值函数图像是 (V) 型。 现在限定在 ([l,r]) ,则

    2024年02月05日
    浏览(115)
  • AtCoder Beginner Contest 336

    给定一个数 (n) ,将 long 中的 o 重复 (n) 次后输出。 模拟即可。 神奇的代码 给定一个数 (n) ,问 (n) 的二进制表示下的末尾零的数量。 即找到最小的 (i) 使得 (n (1 i)) 不为零的位置。枚举即可。 或者直接用内置函数 __builtin_ctz 。(count tail zero? 神奇的代码 定义一个数

    2024年01月20日
    浏览(41)
  • AtCoder Beginner Contest 304

    依次给定每个人的姓名和年龄,排成一圈。从年龄最小的人依次输出姓名。 找到年龄最小的,依次输出就好了。 神奇的代码 给定一个数字,如果其超过三位数,则仅保留其最高三位,低位数字全部置为0。 读一个 string ,直接赋值即可。 神奇的代码 给定 (n) 个人的坐标,第

    2024年02月07日
    浏览(37)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包