AtCoder Beginner Contest 377

上周六咕咕咕了

省流版
  • A. 排序判断即可
  • B. 枚举判断即可
  • C. 记录覆盖位置去重,总数-覆盖数即可
  • D. 枚举右端点,考虑符合条件的左端点数量即可
  • E. 考虑排列的\(i \to p_i\)图,考虑操作数与走的边数关系,利用环循环节算偏移量即可
  • F. 考虑每个皇后实际覆盖的位置,枚举先前皇后计算覆盖交集去重,累加实际覆盖数即可
  • G. 将操作转换成Tries树的节点移动,即一条链上的到达最近叶子的距离,取最小值即可

A - Rearranging ABC (abc377 A)

题目大意

给定三个字母,问能否组成ABC

解题思路

排个序看是否是ABC即可。

神奇的代码
#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;
    sort(s.begin(), s.end());
    if (s == "ABC")
        cout << "Yes" << '\n';
    else
        cout << "No" << '\n';

    return 0;
}



B - Avoid Rook Attack (abc377 B)

题目大意

国际象棋,车,上下左右任意走,

\(8 \times 8\)的棋盘,给定 \(m\)个车的位置。

问有多少位置,不会被车的范围覆盖。

解题思路

一个车就覆盖一行和一列。

因此就set记录被覆盖了的行和列,最后还有\(x\)行和 \(y\)列没覆盖,答案就是 \(xy\)

当然直接花 \(O(8^4)\)枚举位置+判断是否被覆盖也可以。

神奇的代码
#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);
    set<int> r, c;
    for (int i = 0; i < 8; ++i) {
        string s;
        cin >> s;
        for (int j = 0; j < 8; ++j)
            if (s[j] == '#') {
                r.insert(i);
                c.insert(j);
            }
    }
    int ans = (8 - r.size()) * (8 - c.size());
    cout << ans << '\n';

    return 0;
}



C - Avoid Knight Attack (abc377 C)

题目大意

国际象棋,马,八个方向的字走法。

\(n \times n\)的棋盘,给定 \(m\)个马的位置。

问有多少位置,不会被马的范围覆盖。

解题思路

因为一个🐎只覆盖八个位置,用set记录每个每个🐎覆盖的八个位置并去重,假设是去重后有\(x\)位置被🐎覆盖。

答案就是 \(n^2 - x\)

神奇的代码
#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;
    set<array<int, 2>> forbid;
    array<int, 8> dx = {2, 1, -1, -2, -2, -1, 1, 2};
    array<int, 8> dy = {1, 2, 2, 1, -1, -2, -2, -1};
    for (int i = 0; i < m; i++) {
        int x, y;
        cin >> x >> y;
        forbid.insert({x, y});
        for (int j = 0; j < 8; j++) {
            int nx = x + dx[j];
            int ny = y + dy[j];
            if (nx >= 1 && nx <= n && ny >= 1 && ny <= n) {
                forbid.insert({nx, ny});
            }
        }
    }
    LL ans = 1ll * n * n - forbid.size();
    cout << ans << '\n';

    return 0;
}



D - Many Segments 2 (abc377 D)

题目大意

一维数轴,给定若干条线段\([l_i, r_i]\)

\([l,r]\)数量,其不完全包含上述中的任意线段。

解题思路

枚举\(r\),考虑有多少个 \(l\)符合要求。

\([l,r]\)不完全包含任何线段,因此只需考虑 \(r_i \leq r\)的线段,然后 \(l > l_i\)即可。

\(l\)的取值范围即为(\max_{r_i \leq r} l_i, r],个数即为\(r - \max_{r_i \leq r} l_i\),对所有的\(r\)求和即为答案。

对线段的右端点从小到大排序,由于是从小到大枚举的\(r\),因此\(\max_{r_i \leq r} l_i\)可以在枚举的过程 \(O(1)\)维护。

神奇的代码
#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<array<int, 2>> seg(n);
    for (auto& s : seg) {
        cin >> s[0] >> s[1];
    }
    sort(seg.begin(), seg.end(),
         [](const array<int, 2>& a, const array<int, 2>& b) {
             return a[1] < b[1];
         });
    LL ans = 0;
    int maxl = 0;
    int cur = 0;
    for (int i = 1; i <= m; ++i) {
        while (cur < n && seg[cur][1] <= i) {
            maxl = max(maxl, seg[cur][0]);
            ++cur;
        }
        ans += i - maxl;
    }
    cout << ans << '\n';

    return 0;
}



E - Permute K times 2 (abc377 E)

题目大意

给定一个排列\(p_i\),进行\(k\)次操作。

每次操作,同时将所有的 \(p_i\)替换为 \(p_{p_i}\)

问进行\(k\)次操作后的排列。

解题思路

排列的变换,可以考虑建图\(i \to p_i\),即有若干个环。

通过手试会发现,进行第一次操作,相当于走\(1\)条边,进行第二次操作,相当于走\(2\)条边, 第三次走\(4\)条...因为本次走的点,其之前也走过了同样的步数。

即进行 \(k\)次,将会走 \(\sum_{i=0}^{k-1} 2^i = 2^{k} - 1\)条边,由于一个环的循环节大小为环的点数,因此其对环大小取后即为进行了 \(k\)次操作后的位置\(a\),其数就是 \(p_a\)

因此找出每个环,假设环大小\(sz\),用快速幂算出\(2^k - 1 \% sz\) ,即进行 \(k\)次操作后的偏移量,然后求该环上所有点最终的偏移位置即可。

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

long long qpower(long long a, long long b, long long mo) {
    long long qwq = 1;
    while (b) {
        if (b & 1)
            qwq = qwq * a % mo;
        a = a * a % mo;
        b >>= 1;
    }
    return qwq;
}

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int n;
    LL k;
    cin >> n >> k;
    vector<int> p(n);
    for (auto& i : p) {
        cin >> i;
        --i;
    }
    vector<int> vis(n);
    vector<int> ans(n);
    for (int i = 0; i < n; i++) {
        if (vis[i])
            continue;
        int j = i;
        vector<int> cycle;
        while (!vis[j]) {
            vis[j] = 1;
            cycle.push_back(j);
            j = p[j];
        }
        int len = cycle.size();
        for (int i = 0; i < len; i++) {
            int step = (qpower(2, k, len) + len - 1) % len;
            int j = (i + step) % len;
            ans[cycle[i]] = cycle[j];
        }
    }
    for (auto i : ans)
        cout << p[i] + 1 << ' ';

    return 0;
}



F - Avoid Queen Attack (abc377 F)

题目大意

国际象棋,皇后,横竖两个对角线的任意走。

\(n \times n\)的棋盘,给定 \(m\)个皇后的位置。

问有多少位置,不会被皇后的范围覆盖。

解题思路

同样考虑所有皇后覆盖了多少位置,然后用总数减去覆盖的位置。

但与🐎不同的是,一个皇后覆盖的数量位置和\(O(n)\)同级,不能像🐎一样记录所有覆盖位置。

考虑每个皇后实际覆盖的位置数,该位置数是去除了先前考虑的皇后的覆盖位置。

首先,可以算出该皇后可以覆盖的数量,即横竖两个对角线的和。然后考虑去除已经算过的覆盖的位置。

由于\(m \leq 1000\),因此可以直接枚举先前考虑的皇后,然后计算皇后重叠的覆盖部分,就能得到该皇后实际覆盖的位置。

所有的皇后的实际覆盖数的和,用总数减去即为答案。时间复杂度为\(O(m^2)\)

想法比较朴素,实现需要一些细节。

考虑皇后的重叠部份的计算,由于覆盖的部分还会重复覆盖(即皇后\(ab\)重叠的部分,与皇后 \(ac\) 也有重叠),因此计算重复覆盖时还得去重。

如果同行同列或者同对角线, 由于其数量级是\(O(n)\),不能全部记录被覆盖的地方,可以用四个变量表示该行列或对角线是否被覆盖。

然后考虑

  • 皇后的与另外的俩对角线的交集,最多三个点。
  • 皇后的与另外的俩对角线的交集,最多三个点。
  • 皇后的俩对角线与另外的一对角线的交集,最多三个点。

遍历先前考虑的所有皇后,得到这些覆盖的点,去重后即为先前已经考虑过的覆盖的点。用该皇后可以覆盖的位置数减去考虑过的,即为该皇后实际覆盖的点数。

至于计算交集点,两条斜对角线的计算方式需要解一个二元一次方程组。

代码实现里,除row外,其余的下标都是y轴的。注意中心点被重复考虑的情况。

神奇的代码
#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<array<int, 2>> a(m);
    for (auto& x : a)
        cin >> x[0] >> x[1];
    LL tot = 0;
    auto calc_diag = [&](int x, int y) {
        return min(x, n - y + 1) + min(n - x + 1, y) - 1;
    };
    auto calc_diag2 = [&](int x, int y) {
        return min(x, y) + min(n - x + 1, n - y + 1) - 1;
    };
    auto intersect = [&](int x1, int y1, int x2, int y2, int& tmp) {
        int d1 = x1 + y1, d2 = x2 - y2;
        int x = (d1 + d2), y = (d1 - d2);
        if ((x & 1) || (y & 1))
            return false;
        x = x / 2;
        y = y / 2;
        tmp = y;
        return x >= 1 && x <= n && y >= 1 && y <= n;
    };
    auto intersect2 = [&](int x1, int y1, int x2, int y2, int& tmp) {
        int d1 = x1 - y1, d2 = x2 + y2;
        int x = (d1 + d2), y = (d2 - d1);
        if ((x & 1) || (y & 1))
            return false;
        x = x / 2;
        y = y / 2;
        tmp = y;
        return x >= 1 && x <= n && y >= 1 && y <= n;
    };
    auto calc = [&](int x, int y) {
        LL cnt = 0;
        cnt += n;
        cnt += n;
        cnt += calc_diag(x, y);
        cnt += calc_diag2(x, y);
        cnt -= 3;
        return cnt;
    };
    auto in_range = [&](int x) { return x >= 1 && x <= n; };
    for (int i = 0; i < m; i++) {
        auto [x, y] = a[i];
        LL forbid = calc(x, y);
        bool row = false, col = false, diag = false, diag2 = false;
        set<int> forbid_row, forbid_col, forbid_diag, forbid_diag2;
        for (int j = 0; j < i; ++j) {
            auto [x2, y2] = a[j];
            if (x == x2)
                row = true;
            if (y == y2)
                col = true;
            if (x + y == x2 + y2)
                diag = true;
            if (x - y == x2 - y2)
                diag2 = true;

            forbid_row.insert(y2);
            if (in_range(x2 + y2 - x))
                forbid_row.insert(x2 + y2 - x);
            if (in_range(y2 - x2 + x))
                forbid_row.insert(y2 - x2 + x);

            forbid_col.insert(x2);
            if (in_range(x2 + y2 - y))
                forbid_col.insert(x2 + y2 - y);
            if (in_range(x2 - y2 + y))
                forbid_col.insert(x2 - y2 + y);

            int tmp;
            if (in_range(x + y - x2))
                forbid_diag.insert(x + y - x2);
            if (in_range(x + y - y2))
                forbid_diag.insert(y2);
            if (intersect(x, y, x2, y2, tmp))
                forbid_diag.insert(tmp);

            if (in_range(y - x + x2))
                forbid_diag2.insert(y - x + x2);
            if (in_range(x - y + y2))
                forbid_diag2.insert(y2);
            if (intersect2(x, y, x2, y2, tmp))
                forbid_diag2.insert(tmp);
        }
        int center = 0;
        if (row) {
            forbid -= n;
            center++;
        } else {
            forbid -= forbid_row.size();
            center += forbid_row.count(y);
        }
        if (col) {
            forbid -= n;
            center++;
        } else {
            forbid -= forbid_col.size();
            center += forbid_col.count(x);
        }
        if (diag) {
            forbid -= calc_diag(x, y);
            center++;
        } else {
            forbid -= forbid_diag.size();
            center += forbid_diag.count(y);
        }
        if (diag2) {
            forbid -= calc_diag2(x, y);
            center++;
        } else {
            forbid -= forbid_diag2.size();
            center += forbid_diag2.count(y);
        }
        tot += forbid;
        tot += max(center - 1, 0);
    }
    LL ans = 1ll * n * n - tot;
    cout << ans << '\n';

    return 0;
}



G - Edit to Match (abc377 G)

题目大意

给定\(n\)个字符串,对于每个字符串 \(s_i\),回答以下问题。

进行最少次数操作,使得 \(s_i\)为空,或者存在\(j < i\),使得\(s_i = s_j\)

操作分两种:

  • 删去\(s_i\)末尾的字符。
  • \(s_i\)末尾添加任意字符。

解题思路

对尾部操作,对这些字符串建立一颗Trie树,操作一相当于节点往父亲走,操作二相当于往儿子方向走。然后以最小的步数到达叶子或者根。

考虑预处理数组\(mindeep_i\),表示从点 \(i\)出发,到达叶子的最小步数(其实就是子树的最小叶子深度-当前点深度),然后对该字符串所对应的 Tries树的所有节点的\(mindeep_i\)+删去末尾字符到达该点的操作数取个最小值即可。

插入字符串时需要更新 \(mindeep_i\),需要更新的也刚好就是插入时经过的所有节点。

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

const int inf = 1e9 + 7;

const int SZ = 26;
template <typename T, typename K> struct Trie {
    struct node {
        bool is_terminal = false;
        int min_deep = inf;
        array<int, SZ> children{};
    };

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

    vector<node> tree;

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

    int insert(const T& sequence) {
        int cur = 0;
        int ans = sequence.size();
        int cost = sequence.size();
        for (int i = 0; i < (int)sequence.size(); i++) {
            K value = sequence[i];
            if (tree[cur].children[cast(value)] == 0) {
                tree[cur].children[cast(value)] = (int)tree.size();
                tree.push_back(node());
            }
            cur = tree[cur].children[cast(value)];
            --cost;
            ans = min(ans, cost + tree[cur].min_deep);
            tree[cur].min_deep = min(tree[cur].min_deep, cost);
        }
        tree[cur].is_terminal = true;
        return ans;
    }
};

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int n;
    cin >> n;
    Trie<string, char> trie('a');
    for (int i = 0; i < n; i++) {
        string s;
        cin >> s;
        int ans = trie.insert(s);
        cout << ans << '\n';
    }

    return 0;
}



posted @ 2024-10-29 14:46  ~Lanly~  阅读(301)  评论(0编辑  收藏  举报