AtCoder Beginner Contest 309

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

感觉F写了个乱搞做法

A - Nine (abc309 A)

题目大意

给定一个\(3 \times 3\)的网格,以及两个数字。

问这两个数字是否水平相邻

解题思路

求出两个数字的横纵坐标,看是否横坐标相同,纵坐标差一即可。

读题不仔细,开题就WA了。

神奇的代码
#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 a, b;
    cin >> a >> b;
    -- a, -- b;
    if (abs(a % 3 - b % 3) == 1 && abs(a / 3 - b / 3) == 0)
        cout << "Yes" << '\n';
    else 
        cout << "No" << '\n';

    return 0;
}



B - Rotate (abc309 B)

题目大意

给定一个矩形。将最外围的数字顺时针旋转一格。

解题思路

可以模拟一个指针跑,也可以分别考虑四个边界的转移公式。

神奇的代码
#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> a(n);
    for(auto &i : a)
        cin >> i;
    auto ans = a;
    for(int i = 0; i < n; ++ i){
        ans[0][i] = i ? a[0][i - 1]: a[1][0];
        ans[i][0] = i == n - 1 ? a[n - 1][1] : a[i + 1][0];
        ans[n - 1][i] = i == n - 1 ? a[n - 2][n - 1] : a[n - 1][i + 1];
        ans[i][n - 1] = i ? a[i - 1][n - 1] : a[0][n - 2];
    }
    for(auto &i : ans){
            cout << i;
        cout << '\n';
    }

    return 0;
}



C - Medicine (abc309 C)

题目大意

给定\(n\)种药,第 \(i\)种药吃 \(a_i\)天,每天 \(b_i\)粒。

问最早到第几天,吃的药的粒数 \(\leq K\)

解题思路

我们可以从小到大枚举天数,直到某天吃的药的粒数\(\leq K\),但天数高达\(O(10^9)\),直接枚举会超时。

但是我们会发现,每天的吃的药的粒数会越来越少,因此每天吃的药的粒数和天数之间有单调性

于是我们可以二分天数,判断这一天吃的药的粒数是否\(\leq K\),进而排除一半的答案,搜索另一片。

神奇的代码
#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, k;
    cin >> n >> k;
    vector<array<int, 2>> a(n);
    for(auto &i : a)
        cin >> i[0] >> i[1];
    int l = 0, r = 1e9 + 7;
    auto check = [&](int x){
        LL tot = 0;
        for(auto &i : a){
            tot += i[1] * (i[0] >= x);
        }
        return tot > k;
    };
    while(l + 1 < r){
        int mid = (l + r) >> 1;
        if (check(mid))
            l = mid;
        else 
            r = mid;
    }
    cout << r << '\n';

    return 0;
}



D - Add One Edge (abc309 D)

题目大意

给定两个连通块,现在从这两个连通块中各选一个点,连一条边,问\(1\)号点和 \(n_1+n_2\)号点的最小距离的最大值。

解题思路

很显然,从\(1\)号点到 \(n_1+n_2\)号点,其路径是 \(1 \to l \to r \to n_1 + n_2\)。其中\(l\)号点在第一个连通块,\(r\)号点在第二个联通块,是我们选择连边的两个点,其中 \(l \to r\)距离是 \(1\),最大化总距离,那就是最大化 \(1 \to l\)\(r \to n_1 + n_2\)

那就分别从\(1\)号点和 \(n_1 + n_2\)号点分别 \(BFS\),找到最远的距离的点,连边即是答案。

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

const int inf = 1e9 + 7;

int main(void) {
    ios::sync_with_stdio(false); 
    cin.tie(0); cout.tie(0);
    int n1, n2, m;
    cin >> n1 >> n2 >> m;
    vector<array<int, 2>> edges(m);
    vector<vector<int>> e(n1 + n2);
    for(int i = 0; i < m; ++ i){
        int u, v;
        cin >> u >> v;
        -- u, -- v;
        edges[i] = {u, v};
        e[u].push_back(i);
        e[v].push_back(i);
    }
    vector<int> dis(n1 + n2, inf);
    auto BFS = [&](int st){
        dis[st] = 0;
        queue<int> team;
        team.push(st);
        while(!team.empty()){
            int u = team.front();
            team.pop();
            for(auto &i : e[u]){
                int v = u ^ edges[i][0] ^ edges[i][1];
                if (dis[v] > dis[u] + 1){
                    dis[v] = dis[u] + 1;
                    team.push(v);
                }
            }
        }
    };
    BFS(0);
    BFS(n1 + n2 - 1);
    cout << *max_element(dis.begin(), dis.begin() + n1) + *max_element(dis.begin() + n1, dis.end()) + 1 << '\n';

    return 0;
}



E - Family and Insurance (abc309 E)

题目大意

给定一棵(?或者若干棵)树,以及\(m\)次操作。

每次操作将以\(x_i\)号点的子树,到根距离不超过\(y_i\) 的点都加一。

问有多少个点至少加了一。

解题思路

如果将问题简化成一条链,那很显然就是个区间加问题,差分一下,每次操作就变成一个点加一,一个点减一,最后再求一遍前缀和还原数组就解决了。

但本题是树,与链的区别是,每次操作是一个点加一,很多个点减一(并且减一的点不能很快找到)。

但考虑对这棵树\(DFS\),在某一刻,我们维护的信息其实就是当前点到根的这条链,于是问题貌似就解决了?

\(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, m;
    cin >> n >> m;
    vector<vector<int>> edge(n);
    for(int i = 1; i < n; ++ i){
        int v;
        cin >> v;
        -- v;
        edge[v].push_back(i);
    }
    int ans = 0;
    vector<int> visit(n, false);
    vector<int> diff(n + 1, 0);
    int sum = 0;
    vector<int> ins(n, 0);
    for(int i = 0; i < m; ++ i){
        int x, y;
        cin >> x >> y;
        -- x;
        ins[x] = max(ins[x], y);
    }
    function<void(int, int)> dfs = [&](int u, int deep){
        visit[u] = 1;
        if (ins[u]){
            ++ sum;
            diff[min(n, deep + ins[u] + 1)] -= 1;
        }
        sum += diff[deep];
        if (sum)
            ++ ans;
        for(auto &v : edge[u]){
            dfs(v, deep + 1);
        }
        sum -= diff[deep];
        if (ins[u]){
            -- sum;
            diff[min(n, deep + ins[u] + 1)] += 1;
        }
    };
    for(int i = 0; i < n; ++ i){
        if (!visit[i])
            dfs(i, 0);
    }
    cout << ans << '\n';

    return 0;
}



也可以用\(dp\),设 \(dp[i]\)表示从该节点往下,保险还能有效多少代子孙,很显然从父亲转移过来即可。因此从根直接 \(DFS\)求该 \(dp\)值即可。


F - Box in Box (abc309 F)

题目大意

给定\(n\)个三元组,可以交换三元组的元素的顺序。

问是否存在两个三元组,其一个三元组的三个元素都严格大于另一个三元组的三个元素。

解题思路

考虑二元组的做法。

如果不能交换,这就是一个典型的二维偏序问题。

如果能交换,枚举一下交换情况,可以发现在比较的时候,希望让最大值之间比较,最小值之间比较,而不是让一个二元组的最大值和一个二元组的最小值比较。

因此我们将二元组的元素排个序,小的在左,大的在右,然后再按第一位排序,此时可以发现用双指针法就解决了,当然也可以用二维偏序的解法比如树状数组解决。

从左到右枚举枚举一个二元组\(a_i\),然后找到第一个 \(a_j[0] > a_i[0]\),此时如果 \(\max_{k \geq j} a_k[1] > a_i[1]\),那就存在两个二元组满足上述条件了。而\(\max_{k \geq j} a_k[1]\)就是一个后缀最大值,事先预处理一下就可以 \(O(1)\)查询。

二维的解决了,考虑三维。

二维偏序转到三维偏序的一个做法是\(CDQ\)分治(然后花了几分钟学了下\(CDQ\)分治)。注意二维偏序的做法是保证一维有序的情况下处理另一维,而三维的话就是要保证两维都有序的情况下处理第三维。

如何保证两维都有序呢?首先我们对第一维排个序,这样就保证了一维有序,还有一维如何保证?如果我们对整个区间的第二维排序,那么第一维有序就不一定了。

能否不对整个区间排序?而取部分的区间?

首先对于一个区间\([l, r]\)是否存在答案,根据分治,答案的分布有三种可能,要么两个三元组都在 \([l, mid]\),要么都在 \([mid + 1, r]\),要么一个在左区间,一个在右区间,

前两种可能我们可以递归解决,对于第三种可能,我们就要找到两个区间的三元组的偏序关系,由于第一维事先排好序了,因此在总会有 \(a_i \leq a_j, i \in [l, mid], j \in [mid + 1, r]\) ,因此第一维可以忽略,此时就剩下第二维和第三维,问题就变成上述的二维问题,那么我们可以分别对\([l, mid],[mid + 1, r]\)的第二维排序,然后用上述提到的双指针法和后缀最大值解决。

但还有个问题,注意我们的第一维的关系是\(\leq\),而不是题目要求的严格小于,因此此时的第一维实际上不能忽略。万一第一维相等就不能成为答案,但由于我们对第二维排了序,因此第一维的大小是乱序的,似乎就没有什么规律。

细想一下,在之前的\(CDQ\)分治统计不严格偏序数量时,答案的正确性在于第一维关系是\(\leq\),符合题目要求,就可以忽略这一维,而此处的 \(\leq\)并不符合题目要求,不能忽略第一维,如果强行忽略,答案可能会不正确(考虑答案的范围就扩大成\(a_i[0] = a_j[0], a_i[1] < a_j[1], a_i[2] < a_j[2]\)),而如果能保证 \(<\),那么答案的正确性就能保证。为了保证有\(<\),考虑能否可以将\(mid\)设为 \(a_{mid + 1}[0] > a_{mid}[0]\)的地方?

但注意到, \(CDQ\)分治的时间复杂度是 \(O(n\log^2 n)\),其每次分治时的\(mid = \frac{l + r}{2}\) 是一个保证复杂度的关键之处,这样才保证每次分治处理的复杂度是\(f(n) + f(\frac{n}{2}) + f(\frac{n}{2}) + ...\),而每次分治因为涉及到排序,因此 \(f(n) = n\log n\)

为了找一个\(mid\)使得 \(a_{mid + 1}[0] > a_{mid}[0]\),同时又要保证复杂度,可以大胆猜想选择\(a_{mid}[0]\)的数的最左边或最右边(因为对第一维排了序,相同数都在一起,因此分界点就从中间位置左右偏移一下,找到第一个\(\neq a_{mid}[0]\)之处作为分治的分界点。虽然每次分治的区间大小不一定减半,但从值域上感觉会减半,并大胆猜想出题人不会卡,于是按此写了写发现过了。

如何找到从中间位置左右偏移的点,对第一维排序后预处理一下每个第一维值的最小下标和最大下标即可。

神奇的代码
#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, 3>> a(n);
    for(auto &i : a){
        for(auto &j : i)
            cin >> j;
        sort(i.begin(), i.end());
    }
    sort(a.begin(), a.end(), [&](const auto& x, const auto& y){
            return x[0] < y[0];
        });
    map<int, int> posl, posr;
    for(int i = n - 1; i >= 0; -- i){
        posl[a[i][0]] = i;
    }
    for(int i = 0; i < n; ++ i){
        posr[a[i][0]] = i;
    }
    bool ok = 0;
    function<void(int, int)> solve = [&](int l, int r){
        if (l >= r - 1)
            return;
        int mid = posl[a[(l + r) >> 1][0]];
        if (mid <= l)
            mid = posr[a[(l + r) >> 1][0]] + 1;
        if (mid >= r)
            return;
        solve(l, mid);
        solve(mid, r);

        sort(a.begin() + l, a.begin() + mid, [&](const auto& x, const auto&y){
                return x[1] < y[1];
                });
        sort(a.begin() + mid, a.begin() + r, [&](const auto& x, const auto&y){
                return x[1] < y[1];
                });

        vector<int> suf(r - l);
        for(int i = r - 1; i >= l; -- i){
            suf[i - l] = a[i][2];
            if (i < r - 1)
                suf[i - l] = max(suf[i - l], suf[i - l + 1]);
        }
        int pos = mid;
        for(int i = l; i < mid; ++ i){
            while(pos < r && a[pos][1] <= a[i][1])
                ++ pos;
            if (pos == r)
                break;
            if (suf[pos - l] > a[i][2])
                ok = 1;
        }
    };
    solve(0, n);
    if (ok)
        cout << "Yes" << '\n';
    else 
        cout << "No" << '\n';

    return 0;
}



G - Ban Permutation (abc309 G)

题目大意

问关于\(n\)的排列的数量,满足 \(\forall i \in [1,n], |p_i - i| \geq x\)

解题思路

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

神奇的代码



Ex - Simple Path Counting Problem (abc309 Ex)

题目大意

<++>

解题思路

<++>

神奇的代码



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

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

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

相关文章

  • AtCoder Beginner Contest 311

    给定一个字符串,问最短的一个前缀,包含 A B C 这三个字符。 注意到这个前缀的末尾字母一定是这三个字母中的一个,因此答案就是这三个字母出现位置最早的最大值。 神奇的代码 给定 (n) 个人的 (d) 天的空闲与否的情况,问最长的连续天,这 (n) 个人都空闲。 我们找

    2024年02月16日
    浏览(33)
  • AtCoder Beginner Contest 318

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

    2024年02月10日
    浏览(30)
  • AtCoder Beginner Contest 328

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

    2024年02月05日
    浏览(27)
  • AtCoder Beginner Contest 323

    有的人边上课边打abc 给定一个 (01) 字符串,问偶数位(从 (1) 开始) 是否全为 (0) 。 遍历判断即可。 神奇的代码 给定 (n) 个人与其他所有人的胜负情况。问最后谁赢。 一个人获胜次数最多则赢,若次数相同,则编号小的赢。 统计每个人的获胜次数,排个序即可。 神奇

    2024年02月08日
    浏览(22)
  • AtCoder Beginner Contest 299

    给定一个包含 |*. 的字符串,其中 | 两个, * 一个,问 * 是否在两个 | 之间。 找到两个 | 的下标 (l, r) 以及 * 的下标 (mid) ,看看是否满足 (l mid r) 即可。 神奇的代码 给定 (n) 个人的卡片,颜色为 (c_i) ,数字为 (r_i) 。 如果其中有颜色为 (T) 的牌,则该颜色中数字最大

    2023年04月23日
    浏览(15)
  • AtCoder Beginner Contest 324

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

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

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

    2024年04月08日
    浏览(33)
  • AtCoder Beginner Contest 325

    感觉错失了上分机会 给定姓和名,输出尊称,即姓+ san 。 按照题意模拟即可。 神奇的代码 给定 (n) 个地区的公司人数和对应的时区,规定上班时间为 (9:00-18:00) ,现召开一小时会议,上班期间的公司可以参加。问订个时间,能参与的人数的最大值。 枚举开会的时间,然后

    2024年02月08日
    浏览(23)
  • AtCoder Beginner Contest 330

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

    2024年02月05日
    浏览(108)
  • AtCoder Beginner Contest 332

    坐地铁时口糊了6题,回来写时结果爆 long long , 0 没有逆元,卡了好久 线上购物,买了 (n) 种物品,分别给出它们的单价和数量。 若总价少于 (s) 元,则需要支付 (k) 元邮费,否则包邮。 问总价多少。 求个和判断下是否加邮费即可。 神奇的代码 一个容量为 (G) 的玻璃杯

    2024年02月04日
    浏览(31)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包