AtCoder Beginner Contest 221【A - G】

比赛链接:https://atcoder.jp/contests/abc221/tasks

A - Seismic magnitude scales

题意

给出两个正整数 \(a, b\) ,计算 \(32^{a - b}\)

  • \(3 \le b \le a \le 9\)

题解

\(32^{a - b} = 2 ^ {5(a - b)}\)

代码

#include <bits/stdc++.h>
using namespace std;
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int a, b;
    cin >> a >> b;
    cout << (1 << (5 * (a - b))) << "\n";
    return 0;
}

B - typo

题意

给出两个字符串 \(s, t\) ,判断能否交换 \(s\) 中至多一对相邻字符,使得 \(s\)\(t\) 相同。

  • \(2 \le |s| = |t| \le 100\)

题解

枚举交换的位置,同时判断前后的 \(s, t\) 是否相同即可。

代码

#include <bits/stdc++.h>
using namespace std;
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    string s, t;
    cin >> s >> t;
    int n = s.size();
    auto query = [&](int l, int r) {
        for (int i = l; i <= r; i++) {
            if (s[i] != t[i]) {
                return false;
            }
        }
        return true;
    };
    bool ok = (s == t);
    for (int i = 0; i + 1 < n; i++) {
        if (s[i] == t[i + 1] and s[i + 1] == t[i] and query(0, i - 1) and query(i + 2, n - 1)) {
            ok = true;
        }
    }
    cout << (ok ? "Yes" : "No") << "\n";
    return 0;
}

C - Select Mul

题意

给出一个正整数 \(n\) ,将其分为两个非空子序列,子序列可以重新排列。

计算在所有可能的情况中,两个子序列之积的最大值。

  • \(1 \le n \le 10^9\)

题解

枚举所有分类情况即可,时间复杂度约为 \(2^{len(n)}\)

代码

#include <bits/stdc++.h>
using namespace std;
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    string s;
    cin >> s;
    int n = s.size();
    int ans = 0;
    for (int i = 0; i < (1 << n); i++) {
        string a, b;
        for (int j = 0; j < n; j++) {
            (i & (1 << j) ? a : b) += s[j];
        }
        if (a.empty() or b.empty()) {
            continue;
        }
        sort(a.begin(), a.end(), greater<>());
        sort(b.begin(), b.end(), greater<>());
        ans = max(ans, stoi(a) * stoi(b));
    }
    cout << ans << "\n";
    return 0;
}

D - Online games

题意

给出 \(n\) 个区间起点 \(a_i\) 及区间长度 \(b_i\) ,计算有多少个数恰好被 \(1, \dots, n\) 个区间覆盖。

  • \(1 \le n \le 2 \times 10^5\)
  • \(1 \le a_i, b_i \le 10^9\)

题解一

将区间端点离散化后差分进行区间加和,然后遍历一次所有点即可。

代码

#include <bits/stdc++.h>
using namespace std;
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n;
    cin >> n;
    vector<pair<int, int>> seg(n);
    vector<int> disc;
    for (int i = 0; i < n; i++) {
        int a, b;
        cin >> a >> b;
        seg[i] = {a, a + b - 1};
        for (int j = -1; j <= 1; j++) {
            disc.push_back(a + j);
            disc.push_back(a + b - 1 + j);
        }
    }
    sort(disc.begin(), disc.end());
    disc.resize(unique(disc.begin(), disc.end()) - disc.begin());
    const int N = disc.size();
    vector<int> cnt(N);
    map<int, int> mp;
    for (auto [l, r] : seg) {
        int nl = lower_bound(disc.begin(), disc.end(), l) - disc.begin();
        int nr = lower_bound(disc.begin(), disc.end(), r) - disc.begin();
        mp[nl] = l, mp[nr] = r;
        ++cnt[nl], --cnt[nr + 1];
    }
    for (int i = 1; i < N; i++) {
        cnt[i] += cnt[i - 1];
        mp[i] = max(mp[i], mp[i - 1] + 1);
    }
    vector<int> ans(n + 1);
    for (int i = 1; i < N; i++) {
        ans[cnt[i]] += mp[i + 1] - mp[i];
    }
    for (int i = 1; i <= n; i++) {
        cout << ans[i] << " \n"[i == n];
    }
    return 0;
}

题解二

比较巧妙的一种离散化构造方法。

代码

#include <bits/stdc++.h>
using namespace std;
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n;
    cin >> n;
    vector<pair<int, int>> seg;
    for (int i = 0; i < n; i++) {
        int a, b;
        cin >> a >> b;
        seg.emplace_back(a, 1);
        seg.emplace_back(a + b, -1);
    }
    sort(seg.begin(), seg.end());
    vector<int> ans(n + 1);
    int cnt = 0;
    for (int i = 0; i + 1 < 2 * n; i++) {
        cnt += seg[i].second;
        ans[cnt] += seg[i + 1].first - seg[i].first;
    }
    for (int i = 1; i <= n; i++) {
        cout << ans[i] << " \n"[i == n];
    }
    return 0;
}

E - LEQ

题意

给出一个长为 \(n\) 的整数序列 \(a\) ,计算有多少首部小于等于尾部,长度至少为 \(2\) 的子序列,对 \(998244353\) 取模。

  • \(2 \le n \le 3 \times 10^5\)
  • \(1 \le a_i \le 10^9\)

题解

对于 \(a_j\) 来说,以它为尾部满足条件的子序列个数为 \(\sum \limits _{i \lt j, a_i \le a_j} 2^{j - i - 1}\) ,即 \(2 ^ j \times \sum \limits _{i \lt j, a_i \le a_j} \frac{1}{2^{i + 1}}\)

类似权值树状数组的思想,以对之后的数的贡献 \(\frac{1}{2^{i + 1}}\)\(a_i\) 进行更新即可。

代码

#include <bits/stdc++.h>
using namespace std;

constexpr int MOD = 998244353;

int norm(int x) { if (x < 0) { x += MOD; } if (x >= MOD) { x -= MOD; } return x; }
template<class T> T binpow(T a, int b) { T res = 1; for (; b; b /= 2, a *= a) { if (b % 2) { res *= a; } } return res; }

struct Z {
    int x;
    Z(int x = 0) : x(norm(x)) {}
    int val() const { return x; }
    Z operator-() const { return Z(norm(MOD - x)); }
    Z inv() const { assert(x != 0); return binpow(*this, MOD - 2); }
    Z &operator*=(const Z &rhs) { x = 1LL * x * rhs.x % MOD; return *this; }
    Z &operator+=(const Z &rhs) { x = norm(x + rhs.x); return *this; }
    Z &operator-=(const Z &rhs) { x = norm(x - rhs.x); return *this; }
    Z &operator/=(const Z &rhs) { return *this *= rhs.inv(); }
    friend Z operator*(const Z &lhs, const Z &rhs) { Z res = lhs; res *= rhs; return res; }
    friend Z operator+(const Z &lhs, const Z &rhs) { Z res = lhs; res += rhs; return res; }
    friend Z operator-(const Z &lhs, const Z &rhs) { Z res = lhs; res -= rhs; return res; }
    friend Z operator/(const Z &lhs, const Z &rhs) { Z res = lhs; res /= rhs; return res; }
};

struct Fenwick_tree {
    vector<Z> bit;

    Fenwick_tree(int n) : bit(n + 1) {}

    void update(int pos, Z val) {
        for (int i = pos; i < (int)bit.size(); i += i & (-i)) {
            bit[i] += val;
        }
    }

    Z query(int pos) {
        Z res = 0;
        for (int i = pos; i >= 1; i -= i & (-i)) {
            res += bit[i];
        }
        return res;
    }
};

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n;
    cin >> n;
    vector<int> a(n);
    for (int i = 0; i < n; i++) {
        cin >> a[i];
    }
    vector<int> b(a);
    sort(b.begin(), b.end());
    b.resize(unique(b.begin(), b.end()) - b.begin());
    for (auto& i : a) {
        i = lower_bound(b.begin(), b.end(), i) - b.begin() + 1;
    }
    Z ans = 0;
    Fenwick_tree F(n);
    for (int i = 0; i < n; i++) {
        ans += F.query(a[i]) * binpow(Z(2), i);
        F.update(a[i], binpow(Z(2), i + 1).inv());
    }
    cout << ans.val() << "\n";
    return 0;
}

F - Diameter set

题意

给出一棵大小为 \(n\) 的树,计算有多少种染色方案(至少染 \(2\) 个结点),使得任意染色结点之间的距离均为树的直径。

  • \(2 \le n \le 2 \times 10^5\)

题解

将树从任意一条直径中间切开,根据直径长度的奇偶性分为两种情况

  • 奇数:移去中边
  • 偶数:移去与中点相连的边

此时每棵子树内的结点与切开处的根节点距离至多为 \(\lfloor \frac{diam - 1}{2} \rfloor\) ,即一棵子树内最多只可能染一个结点。

设每棵子树内与切开处的根节点距离为 \(\lfloor \frac{diam - 1}{2} \rfloor\) 的结点个数为 \(x_i\) , 答案即 \(\prod \limits (x_i + 1) - \sum \limits x_i - 1\)

含义为从所有染色情况中减去只染一个或不染的情况。

代码

#include <bits/stdc++.h>
using namespace std;

constexpr int MOD = 998244353;

int norm(int x) { if (x < 0) { x += MOD; } if (x >= MOD) { x -= MOD; } return x; }
template<class T> T binpow(T a, int b) { T res = 1; for (; b; b /= 2, a *= a) { if (b % 2) { res *= a; } } return res; }

struct Z {
    int x;
    Z(int x = 0) : x(norm(x)) {}
    int val() const { return x; }
    Z operator-() const { return Z(norm(MOD - x)); }
    Z inv() const { assert(x != 0); return binpow(*this, MOD - 2); }
    Z &operator*=(const Z &rhs) { x = 1LL * x * rhs.x % MOD; return *this; }
    Z &operator+=(const Z &rhs) { x = norm(x + rhs.x); return *this; }
    Z &operator-=(const Z &rhs) { x = norm(x - rhs.x); return *this; }
    Z &operator/=(const Z &rhs) { return *this *= rhs.inv(); }
    friend Z operator*(const Z &lhs, const Z &rhs) { Z res = lhs; res *= rhs; return res; }
    friend Z operator+(const Z &lhs, const Z &rhs) { Z res = lhs; res += rhs; return res; }
    friend Z operator-(const Z &lhs, const Z &rhs) { Z res = lhs; res -= rhs; return res; }
    friend Z operator/(const Z &lhs, const Z &rhs) { Z res = lhs; res /= rhs; return res; }
};

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n;
    cin >> n;
    vector<set<int>> G(n);
    for (int i = 0; i < n - 1; i++) {
        int u, v;
        cin >> u >> v;
        --u, --v;
        G[u].insert(v);
        G[v].insert(u);
    }
    vector<int> dis(n), fa(n);
    function<void(int, int)> dfs1 = [&](int u, int p) {
        fa[u] = (p == -1 ? -1 : p);
        dis[u] = (p == -1 ? 0 : dis[p] + 1);
        for (auto v : G[u]) {
            if (v != p) {
                dfs1(v, u);
            }
        }
    };
    dfs1(0, -1);
    int ua = max_element(dis.begin(), dis.end()) - dis.begin();
    dfs1(ua, -1);
    int ub = max_element(dis.begin(), dis.end()) - dis.begin();
    vector<int> path;
    for (int u = ub; u != -1; u = fa[u]) {
        path.push_back(u);
    }
    reverse(path.begin(), path.end());
    vector<int> root;
    int diam = dis[ub];
    if (diam & 1) {
        int u = path[diam / 2], v = path[diam / 2 + 1];
        G[u].erase(v);
        G[v].erase(u);
        root.push_back(u);
        root.push_back(v);
    } else {
        int u = path[diam / 2];
        root.assign(G[u].begin(), G[u].end());
        for (auto v : G[u]) {
            G[v].erase(u);
        }
        G[u].clear();
    }
    vector<int> v;
    int cnt = 0;
    function<void(int, int, int)> dfs2 = [&](int u, int p, int d) {
        if (d == (diam - 1) / 2) {
            ++cnt;
        }
        for (auto v : G[u]) {
            if (v != p) {
                dfs2(v, u, d + 1);
            }
        }
    };
    for (auto u : root) {
        cnt = 0;
        dfs2(u, -1, 0);
        v.push_back(cnt);
    }
    Z ans = 1;
    for (auto x : v) {
        ans *= (x + 1);
    }
    for (auto x : v) {
        ans -= x;
    }
    ans -= 1;
    cout << ans.val() << "\n";
    return 0;
}

G - Jumping sequence

题意

在一个平面内可以上下左右移动,初始时位于 \((0, 0)\) 处,给出每步移动的距离 \(d_i\) ,判断能否恰好 \(n\) 步后到达 \((a, b)\) 处,如果可以,输出一种方案。

  • \(1 \le n \le 2000\)
  • \(|a|,|b| \le 3.6 \times 10^6\)
  • \(1 \le d_i \le 1800\)
  • 输入均为整数

题解

比较巧妙的一种构造方案。

将原 \((x, y)\) 坐标系逆时针旋转 45° 映射到 \((x + y, x - y)\) 坐标系中,此时上下左右移动的四种情况分别映射到 \((\pm d_i, \pm d_i)\)

然后问题转化为寻找一种对 \(s_i, s'_i\)\(+1, -1\) 赋值方案使得:

\[\sum \limits _{i = 1} ^{n} s_i d_i = x + y, \sum \limits _{i = 1} ^{n} s'_i d_i = x - y \]

两边加上 \(s = \sum \limits _{i = 1} ^{n} d_i\) ,此时问题转化为了寻找一种对 \(t_i, t'_i\)\(0, 1\) 赋值方案使得:

\[\sum \limits _{i = 1} ^{n} t_i d_i = \frac{s + x + y}{2}, \sum \limits _{i = 1} ^{n} t'_i d_i = \frac{s + x - y}{2} \]

使用 bitset 进行状压枚举所有赋值方案即可。

因为转换后坐标系的特殊性,在回溯后输出方案时每一步需同时考虑 \(t_i, t'_i\) 的赋值情况。

代码

#include <bits/stdc++.h>
using namespace std;
constexpr int M = 2000 * 1800 + 10;
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n, a, b;
    cin >> n >> a >> b;
    vector<int> d(n);
    int s = 0;
    for (int i = 0; i < n; i++) {
        cin >> d[i];
        s += d[i];
    }
    bool ok = true;
    for (auto i : {a + b, a - b}) {
        if ((s + i) % 2 != 0 or (s + i) / 2 < 0 or (s + i) / 2 > s) {
            ok = false;
        }
    }
    if (not ok) {
        cout << "No" << "\n";
        return 0;
    }
    int x = (s + a + b) / 2, y = (s + a - b) / 2;
    vector<bitset<M>> bit(n + 1);
    bit[0][0] = 1;
    for (int i = 0; i < n; i++) {
        bit[i + 1] = bit[i] | (bit[i] << d[i]);
    }
    if (not bit[n][x] or not bit[n][y]) {
        cout << "No" << "\n";
        return 0;
    }
    vector<bool> addx(n), addy(n);
    for (int i = n - 1; i >= 0; i--) {
        if (not bit[i][x]) {
            x -= d[i];
            addx[i] = true;
        }
        if (not bit[i][y]) {
            y -= d[i];
            addy[i] = true;
        }
    }
    cout << "Yes" << "\n";
    for (int i = 0; i < n; i++) {
        cout << (addx[i] ? (addy[i] ? 'R' : 'U') : (addy[i] ? 'D' : 'L'));
    }
    cout << "\n";
    return 0;
}

参考

https://atcoder.jp/contests/abc221/editorial/2733

https://atcoder.jp/contests/abc221/editorial/2732

https://atcoder.jp/contests/abc221/editorial/2731

https://atcoder.jp/contests/abc221/editorial/2729

posted @ 2021-10-08 17:00  Kanoon  阅读(280)  评论(0编辑  收藏  举报