AtCoder Beginner Contest 308

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

这几天在收拾东西搬家,先附上代码,晚点补上题解补完了
感觉这次FG都写不太明白

A - New Scheme (abc308 A)

题目大意

给定八个数,问是否满足以下要求:

  • 不严格升序
  • 每个数在\(100 \sim 675\)之间
  • 每个数都是 \(25\)的倍数

解题思路

依次对每个数判断是否符合这三个条件即可。

神奇的代码
#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, 8> a;
    for(auto &i : a)
        cin >> i;
    auto ok = [&](){
        for(int i = 0; i < 8; ++ i){
            if (i && a[i] < a[i - 1])
                return false;
            if (a[i] < 100 || a[i] > 675)
                return false;
            if (a[i] % 25)
                return false;
        }
        return true;
    };
    if (ok())
        cout << "Yes" << '\n';
    else 
        cout << "No" << '\n';

    return 0;
}



B - Default Price (abc308 B)

题目大意

高桥吃\(n\)份寿司,寿司分为很多种类,其中告诉了 \(m\)种寿司的价格,以及没提到的其他种类寿司的统一价格,问需要多少钱。

解题思路

\(map\)记录每种寿司的价格,直接累计计算即可。

当然 \(n\)只有 \(100\),花 \(O(m)\)来计算每份寿司的价格也行。

神奇的代码
#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;
    map<string, int> price;
    vector<string> a(n);
    for(auto &i : a)
        cin >> i;
    vector<string> col(m);
    for(auto &i : col)
        cin >> i;
    int ano;
    cin >> ano;
    for(auto &i : col){
        int p;
        cin >> p;
        price[i] = p;
    }
    int ans = 0;
    for(auto &i : a){
        if (price.find(i) == price.end())
            ans += ano;
        else 
            ans += price[i];
    }
    cout << ans << '\n';

    return 0;
}



C - Standings (abc308 C)

题目大意

抛硬币,给定\(n\)个人抛\(n\)次硬币后的正面次数 \(a\)和反面次数 \(b\)

要求给这\(n\)个人降序排序,排序的关键字为 \(\frac{a}{a+b}\)

解题思路

直接排序就好了。

一般最好避免浮点数比较,因为会有误差,在此处我们要比较\(\frac{a_i}{a_i + b_i} - \frac{a_j}{a_j + b_j}\) 的正负性,可以改为比较\(a_i(a_j + b_j) - a_j(a_i + b_i)\)的正负性,这样就避免浮点数了。

神奇的代码
#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<array<int, 2>> p(n);
    for(auto &i : p)
        cin >> i[0] >> i[1];
    vector<int> id(n);
    iota(id.begin(), id.end(), 0);
    sort(id.begin(), id.end(), [&](int a, int b){
        LL l = 1ll * p[a][0] * (p[b][0] + p[b][1]);
        LL r = 1ll * p[b][0] * (p[a][0] + p[a][1]);
        return l != r ? l > r : a < b;
    });
    for(auto &i : id)
        cout << i + 1 << ' ';
    cout << '\n';

    return 0;
}



D - Snuke Maze (abc308 D)

题目大意

给定一个二维矩阵,问能否从左上走到右下,要求途径的每个格子上的字母组成 snuke的不断重复拼接的形式。

解题思路

直接搜索即可。

考虑搜索的状态,一个是所处的位置(行和列),另一个是已经走过的距离(为了定位要满足snuke中的哪个字母),其状态数只有\(O(5nm)\),转移 \(O(1)\),于是直接记忆化即可。

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

array<int, 4> dx{1, -1, 0, 0};
array<int, 4> dy{0, 0, 1, -1};

int main(void) {
    ios::sync_with_stdio(false); 
    cin.tie(0); cout.tie(0);
    int h, w;
    cin >> h >> w;
    vector<string> tu(h);
    for(auto &i : tu)
        cin >> i;
    string target = "snuke";
    vector<vector<vector<int>>> visit(h, vector<vector<int>>(w, vector<int>(target.size(), 0)));
    function<bool(int, int, int)> dfs = [&](int x, int y, int len){
        visit[x][y][len] = 1;
        if (tu[x][y] != target[len])
            return false;
        if (x == h - 1 && y == w - 1)
            return true;
        for(int i = 0; i < 4; ++ i){
            int nx = x + dx[i], ny = y + dy[i], nlen = (len + 1) % target.size();
            if (nx < 0 || nx >= h || ny < 0 || ny >= w || visit[nx][ny][nlen])
                continue;
            if (dfs(nx, ny, nlen))
                return true;
        }
        return false;
    };
    auto ok = [&](){
        return dfs(0, 0, 0);
    };
    if (ok())
        cout << "Yes" << '\n';
    else 
        cout << "No" << '\n';

    return 0;
}



E - MEX (abc308 E)

题目大意

给定一个数组\(a\),以及同样长度,仅包括字符 MEX的字符串\(s\)

求所有满足 \(i < j < k\),且 \(s_is_js_k=\) MEX\(mex(a_i, a_j, a_k)\)的和。

\(mex(x)\)函数是求不在 \(x\)内的最小非负整数。

解题思路

先考虑找两个字符,比如EX,正常来说我们是从左到右遍历,遇到E时,需要统计所有在该E右边的X的信息,然后得到结果。我们可以从右往左统计每个X对应的数组\(a\)的值,这样对于此时的每一个 E,当前统计的信息就是该E右边的X的信息。

考虑要统计什么信息,假设当前的E对应的数组\(a\)的值是 \(e\),那么所有 EX的组成的mex只有三种情况:

  • \(mex(e, 0)\)
  • \(mex(e, 1)\)
  • \(mex(e, 2)\)

此时以该E开始,右边的所有X组成的EX,对答案的贡献就分这三类,因此我们就记录一下右边有多少个是\(0\)X\(1\)X\(2\)X。然后乘以对应的mex值就是对答案的贡献。这就是我们要统计的信息。

解决了EX,对于MEX,其实可以看成MEX,只是这时候的EX\(6\)种情况:

  • mex(M, 0, 0)
  • mex(M, 0, 1)
  • mex(M, 0, 2)
  • mex(M, 1, 1)
  • mex(M, 1, 2)
  • mex(M, 2, 2)

同样我们就记录EX分别是\(00,01,02,11,12,22\)这些情况的数量,再乘以对应的 mex值,就是该M对答案的贡献。而统计EX 的数量就是刚刚的问题。

代码里记录\(00,01\)的情况用的二进制状压的方法。

神奇的代码
#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> a(n);
    for(auto &i : a)
        cin >> i;
    string s;
    cin >> s;
    array<int, 3> x{};
    array<LL, 8> ex{};
    map<int, int> tr{{0, 1}, {1, 2}, {2, 4}};
    map<int, int> mex;
    auto get_mex = [&](int x, int y, int z){
        set<int> candidate{0, 1, 2, 3};
        if (candidate.count(x))
            candidate.erase(x);
        if (candidate.count(y))
            candidate.erase(y);
        if (candidate.count(z))
            candidate.erase(z);
        return *candidate.begin();
    };
    for(int i = 0; i < 3; ++ i)
        for(int j = 0; j < 3; ++ j)
            for(int k = 0; k < 3; ++ k){
                mex[tr[i] | tr[j] | tr[k]] = get_mex(i, j, k);
            }
    LL ans = 0;
    for(int i = n - 1; i >= 0; -- i){
        if (s[i] == 'X'){
            x[a[i]] ++;
        }else if (s[i] == 'E'){
            for(int j = 0; j < 3; ++ j){
                int status = (tr[a[i]] | tr[j]);
                ex[status] += x[j];
            }
        }else{
            for(int j = 0; j < 8; ++ j){
                ans += mex[tr[a[i]] | j] * ex[j];
            }
        }
    }
    cout << ans << '\n';

    return 0;
}



感觉也可以枚举 E,然后统计左边的 M的信息以及右边X的信息(就是对应的数的个数),然后再枚举它们取值的3\times 3 = 9种情况计算贡献。


F - Vouchers (abc308 F)

题目大意

给定\(n\)个商品的价格,以及 \(m\)个优惠券,每个优惠券只能用于一个商品,且每个商品购买时只能用一个优惠券。

\(i\)个优惠券可以优惠\(d_i\)元,但要求对应商品价格至少 \(l_i( l_i \geq d_i)\)元。

问购买这 \(n\)个商品需要的最少价格。

解题思路

有几种思考方向,可以枚举优惠券,也可以枚举商品。枚举优惠券的话,考虑枚举的顺序,比如以\(d_i\)升序, \(l_i\)的降序。枚举商品的话,考虑以商品价格的升序还是降序枚举。就这几种思考方向,分别思考一下或许会发现一些突破口。

可以发现,以商品价格的升序方式考虑的话,会不断有新的优惠券可以使用,且先前旧的未使用的优惠券也可以使用,这是一个很好的性质,因此我们就可以从这些未使用且可使用的优惠券中选优惠力度最大的。

考虑是否存在反例,因为每个商品只能用一个优惠券,因此每次都只会从未使用中选最好的一个。当考虑一个新商品,同时还有更多的优惠券变得可使用时,这些优惠券不能代替先前的优惠券适用(不满足最低价格),因此也替换不了。

感觉这个贪心貌似就对了。

如果考虑商品价格降序枚举的话,会不断有优惠券变得不可使用,考虑使用门槛低优惠力度大的优惠券,会发现这样的做法不太行。

神奇的代码
#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<int> p(n);
    for(auto &i : p)
        cin >> i;
    vector<array<int, 2>> coupon(m);
    for(auto &i : coupon)
        cin >> i[1];
    for(auto &i : coupon)
        cin >> i[0];
    priority_queue<array<int, 2>> best{};
    sort(p.begin(), p.end());
    sort(coupon.begin(), coupon.end(), [](const auto& a, const auto& b){
            return a[1] < b[1];
            });
    int pos = 0;
    LL ans = 0;
    for(auto &i : p){
        while(pos < m && coupon[pos][1] <= i){
            best.push(coupon[pos]);
            ++ pos;
        }
        int discout = 0;
        if (!best.empty()){
            discout = best.top()[0];
            best.pop();
        }
        ans += i - discout;
    }
    cout << ans << '\n';

    return 0;
}



G - Minimum Xor Pair Query (abc308 G)

题目大意

初始空数组,维护三种操作:

  • 1 x 往数组里加入一个数\(x\)
  • 2 x 往数组里删除一个数\(x\)
  • 3 从数组选两个数,使得异或值最小,求这个最小值

解题思路

不考虑操作1,2,仅考虑如何从数组里选两个异或值最小。很显然选择的两个数,在二进制下第一个不同的位置尽可能的在低位。为此我们可以对这\(n\)个数建立一棵 trie树,发现该问题可以递归的解决。

考虑一个节点\(u\),左儿子(二进制下为\(0\))\(lson\),右儿子(二进制下为\(1\))\(rson\)

  • 如果\(u\)下只有两个数,一个数在 \(lson\)子树内,一个在 \(rson\)内,那以 \(u\)节点为根的子树的最小异或值 \(ans_u = lson \oplus rson\)。一般我们要遍历到叶子才知道这个\(lson,rson\)是多少,但为了减少时间复杂度,可以把这个信息往上提到\(lson,rson\)处,这样就不用遍历到叶子。
  • 如果\(lson\)下有至少两个数,那我们选择的两个数要么都在\(lson\)里,要么都不在 \(lson\)里。那么以\(u\)节点为根的子树的最小异或值 \(ans_u = min(ans_u, ans_{lson})\),而\(ans_{lson}\)是个子问题。
  • 如果\(rson\)下有至少两个数,同样我们选择的两个数要么都在\(rson\)里,要么都不在 \(rson\)里。那么以\(u\)节点的子树的最小异或值 \(ans_u = min(ans_u, ans_{rson})\)\(ans_{rson}\)同样是个子问题。
  • 其余情况\(ans_u\)都是无穷大。

对于叶子节点,如果该叶子对应两个数,那么 \(ans_{leaf} = 0\),否则为无穷大。

考虑修改的话,我们发现上述产生答案的信息(以\(u\)节点为根的子树中数的个数 以及 只有一个数时其数是多少,其实就是叶子信息)是可以实时维护的:插入就是个数\(+1\),再判断数的个数是不是 \(1\),是的话同步一下儿子的信息。于是问题就解决了。

两个信息的维护其实都是一个很简单的\(dp\)

  • \(cnt_u\)表示以 \(u\)节点为根的子树的数的个数,则 \(cnt_u = cnt_{lson} + cnt_{rson}\)
  • \(value_u\)表示以 \(u\)节点为根的子树中,如果数的个数为\(1\),其值是多少,则 \(value_u = value_{lson} [cnt_{lson} == 1] + value_{rson} [cnt_{rson} == 1]\)

\(ans_u\)就根据上述的情况就可以得到了。

考虑到信息的维护,trie树插入得写成递归形式,因为插入后要合并信息,更新答案。

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

const int inf = (1 << 30) + 114514;
const int SZ = 2;
template<typename T, typename K>
struct Trie {
    struct node {
        K value;
        int ans;
        int vis_count;
        array<int, SZ> children;

        node(K val) : value(val) {
            vis_count = 0;
            fill(children.begin(), children.end(), 0);
            ans = inf;
        }
    };

    int cast(K val) {
        int ret = val;  
        assert(ret < SZ and ret >= 0);
        return ret;
    }

    vector<node> tree;

    Trie(K val) {
        tree.push_back(node(val));
    }

    void insert(int v, int pos, int cur, const T &sequence, bool remove = false) {

        if (remove) {
            tree[cur].vis_count -= 1;
        } else {
            tree[cur].vis_count += 1;
        }

        if (pos < sequence.size()){

            K value = sequence[pos];
            if (tree[cur].children[cast(value)] == 0) {
                tree[cur].children[cast(value)] = (int) tree.size();
                tree.emplace_back(v);
            }

            insert(v, pos + 1, tree[cur].children[cast(value)], sequence, remove);

            if (tree[cur].vis_count == 1){
                int lson = tree[cur].children[cast(value)];
                int rson = tree[cur].children[cast(value) ^ 1];
                if (lson != 0 && tree[lson].vis_count != 0)
                    tree[cur].value = tree[lson].value;
                else 
                    tree[cur].value = tree[rson].value;
            }

            tree[cur].ans = inf;
            int lson = tree[cur].children[0], rson = tree[cur].children[1];
            if (lson != 0 && tree[lson].vis_count > 1)
                tree[cur].ans = min(tree[cur].ans, tree[lson].ans);
            if (rson != 0 && tree[rson].vis_count > 1)
                tree[cur].ans = min(tree[cur].ans, tree[rson].ans);
            if (lson != 0 && rson != 0 && tree[lson].vis_count == 1 && tree[rson].vis_count == 1)
                tree[cur].ans = tree[lson].value ^ tree[rson].value;
        }else{
            tree[cur].ans = inf;
            if (tree[cur].vis_count > 1)
                tree[cur].ans = 0;
        }
    }

    void remove(int v, const T& sequence) {
        insert(v, 0, 0, sequence, true);
    }

    int get_min(){
        return tree.front().ans;
    }

};

int main(void) {
    ios::sync_with_stdio(false); 
    cin.tie(0); cout.tie(0);
    Trie<array<int, 30>, int> tree(0);
    auto tr2bin = [](int x){
        array<int, 30> bin{};
        for(int i = 0; i < 30; ++ i){
            bin[i] = ((x >> (29 - i)) & 1);
        }
        return bin;
    };

    int q;
    cin >> q;
    while(q--){
        int op;
        cin >> op;
        if (op == 1){
            int x;
            cin >> x;
            tree.insert(x, 0, 0, tr2bin(x));
        }else if (op == 2){
            int x;
            cin >> x;
            tree.remove(x, tr2bin(x));
        }else{
            cout << tree.get_min() << '\n';
        }
    }

    return 0;
}



看了看题解发现异或最小有个神奇的性质:其最小值一定相邻两个数之间,这里数是升序排序的。证明的话题解也有,通俗点讲就是考虑证明\(min(x \oplus y, y \oplus z) < x \oplus z\)。考虑\(x\)\(z\) 二进制下从高到低位第一个不同的地方 ,其\(x\)\(0\)\(z\)\(1\)(因为 \(x < z\)),而 \(y\)再此处的值要么是 \(0\)要么是 \(1\),肯定与 \(x\)\(z\)相同,因此其异或值在该位是 \(0\),因此可以更小。

有了这个性质之后,我们可以用一个multiset维护这个序列,用于插入操作、以及删除操作的查找和删除,再用另一个multiset维护相邻两数的异或值。

神奇的代码
#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);

    multiset<int> a, ans;
    int q;
    cin >> q;
    while(q--){
        int op;
        cin >> op;
        if (op == 1){
            int x;
            cin >> x;
            if (!a.empty()){
                auto nxt = a.upper_bound(x), pos = (nxt == a.begin() ? nxt : prev(nxt));
                if (nxt != a.begin()){
                    ans.extract(*pos ^ *nxt);
                    ans.insert(*pos ^ x);
                }
                if (nxt != a.end()){
                    ans.insert(x ^ *nxt);
                }
            }
            a.insert(x);
        }else if (op == 2){
            int x;
            cin >> x;
            auto pos = a.lower_bound(x), prv = (pos == a.begin() ? pos : prev(pos)), nxt = next(pos);
            if (pos != a.begin()){
                ans.extract(*prv ^ *pos);
            }
            if (nxt != a.end())
                ans.extract(*pos ^ *nxt);
            if (pos != a.begin() && nxt != a.end())
                ans.insert(*prv ^ *nxt);
            a.extract(x);
        }else{
            cout << *ans.begin() << '\n';
        }
    }

    return 0;
}



Ex - Make Q (abc308 Ex)

题目大意

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

解题思路

<++>

神奇的代码



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

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

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

相关文章

  • 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 321

    给定一个数,问从高位到低位,数字是不是递减的。 可以以字符串读入,然后依次判断即可。 神奇的代码 给定 (n-1) 个数字,问最后一个数字最少是多少,使得你的分数不小于 (x) 。 数字在 ([0,100]) 之间。分数是去掉最低最高后的和。 因为范围不大,直接枚举最后一个数

    2024年02月08日
    浏览(31)
  • AtCoder Beginner Contest 335

    给定一个字符串,将最后一位改成 4 。 模拟即可。 神奇的代码 给定 (n) ,按字典序输出非负整数 (i,j,k) ,使得 (i+j+kleq n) (n) 只有 (21) ,直接 (O(n^3)) 枚举判断即可。 神奇的代码 二维网格,贪吃蛇,移动,进行 (q) 次操作,分两种 指定贪吃蛇下一步移动的方向 指定

    2024年01月19日
    浏览(32)
  • AtCoder Beginner Contest 322

    给定一个字符串,找到最先出现 ABC 的位置。 直接查找判断即可。 神奇的代码 给定字符串 s 和 t ,问 s 是不是 t 的前缀和后缀。 根据前后缀定义判断即可。这里试了下 python 神奇的代码 (n) 天,有 (m) 天会放烟花。 问每一天,距离未来最近的放烟花的天数。 两个双指针一

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

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

    2024年02月04日
    浏览(55)
  • AtCoder Beginner Contest 303

    给定两个字符串,问这两个字符串是否相似。 两个字符串相似,需要每个字母,要么完全相同,要么一个是 1 一个是 l ,要么一个是 0 一个是 o 按照题意模拟即可。 可以将全部 1 换成 l ,全部 0 换成 o ,再判断相等。 神奇的代码 给定 m 个 n 的排列,问有多少对 ((i, j),i j

    2024年02月07日
    浏览(35)
  • AtCoder Beginner Contest 330

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

    2024年02月05日
    浏览(116)
  • AtCoder Beginner Contest 318

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

    2024年02月10日
    浏览(40)
  • AtCoder Beginner Contest 334

    给定两个数 (b,g(b neq g)) ,如果 (b) 大则输出 Bat ,否则输出 Glove 。 比较大小输出即可。 神奇的代码 给定 (a,m,l,r) ,问有多少个整数 (k) 满足 (l leq a + mk leq r) . 对不等式化简一下即为 (frac{l - a}{m} leq k leq frac{r - a}{m}) 的整数个数。 答案就是 (lfloor frac{r - a}{m} r

    2024年02月04日
    浏览(36)
  • AtCoder Beginner Contest 349

    (n) 个人游戏,每局有一人 (+1) 分,有一人 (-1) 分。 给定最后前 (n-1) 个人的分数,问第 (n) 个人的分数。 零和游戏,所有人总分是 (0) ,因此最后一个人的分数就是前 (n-1) 个人的分数和的相反数。 神奇的代码 对于一个字符串,如果对于所有 (i geq 1) ,都有恰好

    2024年04月13日
    浏览(56)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包