首页 > 其他分享 >【2023.07.14】Atcoder:past201912 - 第一回 アルゴリズム実技検定(div4+区域赛难度)过题小记

【2023.07.14】Atcoder:past201912 - 第一回 アルゴリズム実技検定(div4+区域赛难度)过题小记

时间:2023-07-17 22:57:01浏览次数:34  
标签:Atcoder ver 14 int auto eve 过题 vector odd

G - Division

解法一:位运算+状压枚举(赛时思路)

范围显然,可以跑 \(2^n\) 的算法,考虑位运算状态压缩。以 \(\mathcal O(2^n \cdot 2^n)\) 的复杂度分别枚举位于第一组、第二组中的人,随后计算每一种分组的快乐值,代码较长,赛时敲了半个小时,不过好在一发过了。

总结:其实代码里面的剪枝完全可以不要的,先一口气枚举,随后再计算快乐值会比较容易写一些。随后,下方代码中补全了给定数组的另一半(刚开始没补打算用特判的,这里花费了一些不必要的时间),这样做也能方便后续计算快乐值。

signed main() {
    int n;
    cin >> n;
    
    vector ver(n, vector<int>(n));
    for (int i = 0; i < n; i++) {
        for (int j = 0; j <= i; j++) {
            ver[i][j] = ver[j][i];
        }
        for (int j = i + 1; j < n; j++) {
            cin >> ver[i][j];
        }
    }
    
    int Max = -numeric_limits<int>::max();
    for (int i = 0; i < (1 << n); i++) { // 枚举:O(1024)
        bitset<10> val = i;
        vector<int> g1, g23;
        for (int j = 0; j < n; j++) {
            if (val[j]) g1.push_back(j);
            else g23.push_back(j);
        }
        
        // 剪枝:先计算第一组人的haapy
        int pre = 0;
        for (auto it : g1) {
            for (auto jt : g1) {
                pre += ver[it][jt];
            }
        }
        
        int m = g23.size();
        for (int j = 0; j < (1 << m); j++) { // 枚举:O(1024)
            int num = pre;
            bitset<10> val = j;
            vector<int> g2, g3;
            for (int k = 0; k < m; k++) {
                if (val[k]) g2.push_back(k);
                else g3.push_back(k);
            }
            
            // 至此三组人已经分出,分别计算第二、三组人的happy
            for (auto it : g2) {
                for (auto jt : g2) {
                    int It = g23[it];
                    int Jt = g23[jt];
                    num += ver[It][Jt];
                }
            }
            for (auto it : g3) {
                for (auto jt : g3) {
                    int It = g23[it];
                    int Jt = g23[jt];
                    num += ver[It][Jt];
                }
            }
            Max = max(Max, num);
        }
    }
    cout << Max / 2 << endl;

搜索枚举(官方思路)

这个思路就简便很多了,主体和“八皇后问题”很像,先操作后回溯。平时写这种搜索比较少,赛时确实没想到。

signed main() {
    int n;
    cin >> n;
    
    vector ver(n + 1, vector<int>(n + 1));
    for (int i = 1; i <= n; i++) {
        for (int j = i + 1; j <= n; j++) {
            cin >> ver[i][j];
            ver[j][i] = ver[i][j];
        }
    }
    
    vector<vector<int>> group(4);
    int ans = -numeric_limits<int>::max();
    auto dfs = [&](auto self, int x, int val) -> void {
        if (x == n + 1) {
            ans = max(ans, val);
            return;
        }
        
        for (int i = 1; i <= 3; i++) { // 暴力枚举应该放到哪一组里去
            int add = 0;
            for (auto it : group[i]) {
                add += ver[x][it];
            }
            
            group[i].push_back(x);
            self(self, x + 1, val + add);
            group[i].pop_back(); // 回溯
        }
    };
    dfs(dfs, 1, 0);
    
    cout << ans << endl;
}

H - Bulk Selling

解法一:线段树(赛时思路)

显然,这是一道线段树能够解决的题目,问题在于有一个操作是对奇数位置进行的,显然,传统的线段树难以区分叶子节点的奇偶性——所以,我在这题中建立了两棵线段树,分别负责奇数、偶数位置。

实现难度并不高,只需要修改使得其能够实现“修改、查询区间最小值”即可,但是要注意细节。赛时敲了四十分钟,然后调BUG调了二十分钟,略显逊色……

总结:需要留心的地方在于,如果不存在偶数位置,我们需要特判建一棵只含有一个极大值节点的偶数线段树。

如果不为一个节点,有可能在 build 函数中出现 build(1, 0); 这样的越界情况(导致RE);

如果不包含一个极大值节点,在计算操作三的最小值时可能出现 \(0\) 的情况。

signed main() {
    int n;
    cin >> n;
    
    int ne = n / 2, no = (n + 1) / 2; // 分别计算两棵线段树的节点数目
    int flag = 0;
    if (ne == 0) { // 偶数可能不存在,导致建树时RE
        ne = 1;
        flag = 1;
    }
    Segt eve(ne), odd(no);
    
    for (int i = 1; i <= n; i++) {
        int x;
        cin >> x;
        if (i % 2) {
            odd.w[(i + 1) / 2] = x;
        } else {
            eve.w[i / 2] = x;
        }
    }
    if (flag) { // 需要塞一个极大值节点
        eve.w[1] = INF;
    }
    eve.build(1, ne);
    odd.build(1, no);
    
    
    int q, ans = 0;
    cin >> q;
    while (q--) {
        int op;
        cin >> op;
        
        if (op == 1) {
            int x, a;
            cin >> x >> a;
            if (x % 2) {
                x = (x + 1) / 2;
                if (odd.ask(x, x) >= a) {
                    odd.modify(x, x, -a);
                    ans += a;
                }
            } else {
                x = x / 2;
                if (eve.ask(x, x) >= a) {
                    eve.modify(x, x, -a);
                    ans += a;
                }
            }
        } else if (op == 2) {
            int a;
            cin >> a;
            if (odd.ask(1, no) >= a) {
                odd.modify(1, no, -a);
                ans += no * a;
            }
        } else {
            int a;
            cin >> a;
            if (min(odd.ask(1, no), eve.ask(1, ne)) >= a) {
                odd.modify(1, no, -a);
                eve.modify(1, ne, -a);
                ans += n * a;
            }
        }
    }
    
    cout << ans << endl;
}

我的线段树封装参考(2023.07.10封装):

struct Segt {
    struct node {
        int l, r;
        int w, Min;
        int lazy;
    };
    vector<int> w;
    vector<node> t;
    #define GL (k << 1)
    #define GR (k << 1 | 1)
    
    Segt(int n) : w(n + 1), t((n << 2) + 1) {}
    void pushdown(node &p, int lazy) { // 在此更新下递函数
        p.w += (p.r - p.l + 1) * lazy;
        p.Min += lazy;
        p.lazy += lazy;
    }
    void pushup(node &p, node &l, node &r) { // 在此更新上传函数
        p.w = l.w + r.w;
        p.Min = min(l.Min, r.Min);
    }
    void pushdown(int k) { // 不需要动
        if (!t[k].lazy) return;
        pushdown(t[GL], t[k].lazy);
        pushdown(t[GR], t[k].lazy);
        t[k].lazy = 0;
    }
    void pushup(int k) { // 不需要动
        pushup(t[k], t[GL], t[GR]);
    }
    void build(int l, int r, int k = 1) {
        if (l == r) {
            t[k] = {l, r, w[l], w[l], 0};
            return;
        }
        t[k] = {l, r, 0, 0, 0};
        int mid = (l + r) / 2;
        build(l, mid, GL);
        build(mid + 1, r, GR);
        pushup(k);
    }
    void modify(int l, int r, int val, int k = 1) { //区间修改
        if (l <= t[k].l && t[k].r <= r) {
            pushdown(t[k], val);
            return;
        }
        pushdown(k);
        int mid = (t[k].l + t[k].r) / 2;
        if (l <= mid) modify(l, r, val, GL);
        if (mid < r) modify(l, r, val, GR);
        pushup(k);
    }
    int ask(int l, int r, int k = 1) { //区间询问,不合并
        if (l <= t[k].l && t[k].r <= r) {
            return t[k].Min;
        }
        pushdown(k);
        int mid = (t[k].l + t[k].r) / 2;
        int ans = INF;
        if (l <= mid) ans = min(ans, ask(l, r, GL));
        if (mid < r) ans = min(ans, ask(l, r, GR));
        return ans;
    }
    void debug(int k = 1) {
        cout << "[" << t[k].l << ", " << t[k].r << "]: ";
        cout << "Min = " << t[k].Min << ", ";
        cout << "w = " << t[k].w << ", ";
        cout << endl;
        if (t[k].l == t[k].r) return;
        debug(GL), debug(GR);
    }
    #undef GL
    #undef GR
};

解法二:模拟(官方思路)

细细想来好像确实是用不到线段树维护。

标签:Atcoder,ver,14,int,auto,eve,过题,vector,odd
From: https://www.cnblogs.com/WIDA/p/17555207.html

相关文章

  • 题解 P4815 [CCO2014] 狼人游戏
    看题目限制,可以发现如果将机器人作为点,指控和保护关系作为边,可以建出一个森林,就下来就是传统的树形背包了。设\(f_{i,j,0/1}\)表示当前点为\(i\),子树内有\(j\)个狼人,当前点是否为狼人的方案数。初始化:\(f_{u,0,0}=f_{u,1,1}=1\)当前点为狼:指控:\(f_{u,j,1}=f_{u,j-k,......
  • C++14指北:花里胡哨的C++
    类型!在最经典的C++代码中,我们使用类似类型名变量名=表达式;的形式声明并初始化变量,例如intx=1;inty=x;在上面代码中,我们知道y理应与x的类型相同,但是在上面代码中,如果我们后来把x的类型修改为int64_t,而忘记对应地修改y的类型,则可能导致灾难性的后果。对......
  • freee Programming Contest 2023(AtCoder Beginner Contest 310)
    Preface打的就是依托答辩,当时看一眼D感觉是个爆搜不想写就先跳了去想F,结果傻逼了没想出来最后30min了赶紧溜回去把D爆搜写了,但是已经罚时爆炸了,其实如果正常正序做的话排名会挺稳的后面一问包大爷发现F是个傻逼题,只能说计数水平实在是低下A-OrderSomethingElse签到题#i......
  • CVE-2023-1454注入分析复现
    简介JeecgBoot的代码生成器是一种可以帮助开发者快速构建企业级应用的工具,它可以通过一键生成前后端代码,无需写任何代码,让开发者更多关注业务逻辑。影响版本Jeecg-Boot<=3.5.1环境搭建idea+后端源码:https://github.com/jeecgboot/jeecg-boot/archive/refs/tags/v3.5.0.zip......
  • 【14.0】Django框架之CBV添加装饰器的三种方式
    【一】给类方法加装饰器指名道姓的装--放在方法上面路由path('login_view/',views.MyLogin.as_view()),需要导入一个模块fromdjango.utils.decoratorsimportmethod_decorator视图fromdjango.viewsimportViewfromdjango.utils.decoratorsimportmetho......
  • AtCoder Beginner Contest 310
    PeacefulTeams\(n\)个运动员,要分成\(t\)个队伍,一共有\(m\)对人不能放在一支队伍里,求方案数,每支队伍至少需要有一个人\(1\leqt\leqn\leq10\)题解:DFS搜索通过数据范围考虑爆搜我们考虑枚举的顺序\(O(n!)\)我们对每个人\(c\)枚举将其加到当前的\(d\)支队伍中新开一......
  • A014 《太阳系的秘密》编程 源码
    一、课程介绍在本节课中,将会了解太阳系的基本情况,绘制出一个太阳系。在这个过程中,理解for循环结合列表的使用方法,掌握使用random.randint(a,b)产生随机整数的方法。二、知识重难点解析利用列表实现for循环将for循环后边的range()替换成列表后,for循环会按顺序依次提取列......
  • AtCoder Regular Contest 092 E Both Sides Merger
    洛谷传送门AtCoder传送门Keyobservation:每个元素的下标奇偶性不改变。于是讨论最后一个数是下标为奇数还是偶数加起来的数。将下标奇偶性相同的元素分开考虑。对于下标奇偶性相同的元素,不难发现答案的上界是所有\(>0\)的元素之和(没有\(>0\)的元素时选一个最大的),并且一......
  • freee Programming Contest 2023(AtCoder Beginner Contest 310)
    freeeProgrammingContest2023(AtCoderBeginnerContest310)-AtCoderA-OrderSomethingElse(atcoder.jp)题意是在买一道菜的情况下可以将原为\(P\)元的饮料优惠到\(Q\)元,否则就按原价买#include<bits/stdc++.h>#defineintlonglongusingnamespacestd;signed......
  • Day09(2023.07.14)
    行程8:45    到达上海市信息安全测评认证中心(黄浦区陆家浜路1308号)9:00  学习软件测评工具11:30--13:00   吃饭休息13:00  学习软件测评工具17:00      下班......