跳转至

ABC377(A-G)

A - Rearranging ABC

题目大意

给你一个长度为 \(3\) 的由大写字母组成的字符串 \(S\),判断能否重排 \(S\) 使其等于 ABC

参考代码
#include <algorithm>
#include <iostream>

using namespace std;

int main()
{
    string s;
    cin >> s;
    sort(s.begin(), s.end());
    cout << (s == "ABC" ? "Yes" : "No") << endl;
    return 0;
}

B - Avoid Rook Attack

题目大意

给你一个 \(8 \times 8\) 的棋盘,每个位置如果是 . 表示上面没有棋子,如果是 # 表示上面有国际象棋的车。国际象棋的车能攻击到同一行、同一列的棋子。问:棋盘上有多少个位置不会被任何车攻击到?

参考代码
#include <iostream>
#include <vector>

using namespace std;

int main()
{
    const int n = 8;
    vector<string> g(n);
    vector<bool> row(n), col(n);
    for (int i = 0; i < n; i++)
    {
        cin >> g[i];
        for(int j = 0; j < n; j++)
            if(g[i][j] == '#')
            {
                row[i] = true;
                col[j] = true;
            }
    }
    int ans = 0;
    for(int i = 0; i < n; i++)
        for(int j = 0; j < n; j++)
            if(!row[i] && !col[j])
                ans++;
    cout << ans << endl;
    return 0;
}

C - Avoid Knight Attack

题目大意

有一个 \(N \times N(1 \le N \le 10 ^ 9)\) 的棋盘,棋盘上有 \(M(1 \le M \le 2 \times 10 ^ 5)\) 个国际象棋的马,第 \(i\) 个马的位置是 \((a_i, b_i)\) 。国际象棋的马能够攻击到“日”形状的位置,且不受蹩马腿的限制。问:棋盘上有多少个位置不会被任何马攻击到?

解题思路

因为 \(N\) 很大,但是 \(M\) 较小,只需要记录下所有被马攻击到的位置,用合法的位置去减就可以。

参考代码
#include <iostream>
#include <set>

using namespace std;

int main()
{
    int n, m;
    cin >> n >> m;
    set<pair<int, int>> vis;

    int dr[] = {-1, 1, -2, 2, -2, 2, -1, 1};
    int dc[] = {-2, -2, -1, -1, 1, 1, 2, 2};

    auto in_lim = [n](int r, int c) -> bool
    { return r > 0 && r <= n && c > 0 && c <= n; };

    while (m--)
    {
        int r, c;
        cin >> r >> c;
        for (int i = 0; i < 8; i++)
        {
            int nr = r + dr[i];
            int nc = c + dc[i];
            if (in_lim(nr, nc))
                vis.insert({nr, nc});
        }
        vis.insert({r,c});
    }

    cout << (long long)n * n - vis.size() << endl;
    return 0;
}

D - Many Segments 2

题目大意

给你 \(N(1 \le N \le 2 \times 10 ^ 5)\) 个闭区间和一个整数 \(M(1 \le M \le 2 \times 10 ^ 5)\),第 \(i\) 个闭区间为 \([L_i, R_i]\),其中 \(1 \le L_i \le R_i \le M\)。求出有多少个数对 \((l, r)\) 满足以下条件:

  • \(1 \le l \le r \le M\)
  • 对于任意的 \(1 \le i \le N\),区间 \([L_i, R_i]\) 不被 \([l, r]\) 完全包含。
解题思路

对于两个区间,\([L_i, R_i]\)\([L_j, R_j]\),如果 \(R_i = R_j\),则实际只需要考虑 \(L\) 更大的区间,因为更大 \(L\) 意味着区间更小,也就更容易被包含。

\(f[r]\) 表示对于某个特定的 \(r\) 值,满足条件的 \(l\) 值的最小值。也就是当 \(f[r] \le l \le r\) 就能满足条件。这样,对于固定的 \(r\) 值,其贡献的 \((l, r)\) 数量就是 \(r-f[r]+1\)。初始的时候我们有 \(f[0] = 1\)

\(f[r]\) 的含义表示 \([f[r], r]\) 不会完全包含任何区间,而 \([f[r]-1, r]\) 会完全包含一个区间从而不满足条件。于是我们可以挖掘一个性质,也就是 \(f[r] \ge f[r-1]\),正确性可以用反证法证明。

于是我们遍历 \(r\) 值,当 \(r\) 值向右扩充的时候,考虑是否有某个区间是 \([L_i, r]\),如果有这样的区间,为了避免完全包含进来,\(l\) 的值至少需要是 \(L_i+1\),于是我们可以得到 \(f[r] = \max(f[r-1], L_i+1)\)。这样就做完了。

参考代码
#include <iostream>
#include <map>

using namespace std;
using LL = long long;

int main()
{
    int n, m;
    cin >> n >> m;
    map<int, int> l_map;
    for (int i = 0; i < n; i++)
    {
        int l, r;
        cin >> l >> r;
        l_map[r] = max(l_map[r], l);
    }
    l_map[m+1] = m+1;
    auto it = l_map.begin();
    int nr = it->first, nl = it->second;
    int l = 1;
    LL ans = 0;
    for(int r = 1; r <= m; r++)
    {
        if(r == nr)
        {
            l = max(l, nl + 1);
            it++;
            nr = it->first;
            nl = it->second;
        }
        ans += r - l + 1;
    }
    cout << ans << endl;
    return 0;
}

E - Permute K times 2

题目大意

给你一个 \(1\)\(N(2 \times 10 ^ 5)\) 的排列 \(P = (P_1, P_2, \cdots, P_N)\),执行以下操作 \(K(1 \le K \le 10 ^ {18})\) 次:

  • 对于 \(i = 1, 2, ..., N\)同时\(P_i\) 替换为 \(P_{P_i}\)

输出 \(K\) 次操作后的序列。

解题思路

之前做过两道类似的题目:ABC367-EABC371-G 但这道题有一个不太一样的地方,前面两道题有两个序列,一个是不变的序列指导如何置换,另一个序列的数字进行换位。但是这道题只有一个序列,既是置换序列,也要交换自己的位置。推导一下规律。不妨设 \(f[i][j]\) 表示进行了 \(i\) 次操作后第 \(j\) 个位置的值。则有:

\[ \begin{align*} f[0][j] &= P_j \\ f[1][j] &= f[0][f[0][j]] = P_{P_j} = P^2_j\\ f[2][j] &= f[1][f[1][j]] = P^2_{f[1][j]} = P^4_j \end{align*} \]

因此我们有 \(f[K][j] = P^{2^K}_j\),这个计算式仅仅依赖于初始的排列 \(P\),也就是替换一次的结果,于是我们和前面的解法一样,将数字的替换关系处理成环,每个位置置换 \(2^K\) 轮,求出环的长度 \(m\),相当于每个环置换 \(2^K \bmod m\) 次。

参考代码
#include <iostream>
#include <vector>

using namespace std;
using LL = long long;

int main()
{
    int n;
    LL k;
    cin >> n >> k;
    vector<int> p(n);
    for(int i = 0; i < n; i++)
    {
        cin >> p[i];
        p[i]--;
    }
    vector<bool> vis(n);
    vector<int> ans(n);

    auto quick_pow = [](LL x, LL a, LL mod) -> LL
    {
        LL res = 1;
        while(a)
        {
            if(a & 1)
                res = res * x % mod;
            x = x * x % mod;
            a >>= 1;
        }
        return res;
    };

    for(int i = 0; i < n; i++)
    {
        if(vis[i])
            continue;
        int u = i;
        vector<int> idx;
        while(!vis[u])
        {
            vis[u] = true;
            idx.push_back(u);
            u = p[u];
        }
        int len = idx.size();
        LL d = quick_pow(2, k, len);
        d = (d - 1 + len) % len;
        for(int x = 0, y = d; x < len; x++, y = (y + 1) % len)
        {
            int u = idx[x], v = idx[y];
            ans[u] = p[v];
        }
    }
    for(int i = 0; i < n; i++)
        cout << ans[i] + 1 << ' ';
    return 0;
}

F - Avoid Queen Attack

题目大意

有一个 \(N \times N(1 \le N \le 10 ^ 9)\) 的棋盘,棋盘上有 \(M(1 \le M \le 2 \times 10 ^ 3)\) 个国际象棋的皇后,第 \(i\) 个皇后的位置是 \((a_i, b_i)\) 。国际象棋的皇后能够攻击到同一行、同一列、同一条主对角线和副对角线的棋子。问:棋盘上有多少个位置不会被任何皇后攻击到?

解题思路

因为 \(N\) 很大。如果标记每一行、列、主副对角线有皇后,然后枚举每一个点的话,时间复杂度达到 \(O(N^2)\),过不了。

考虑找出有多少个点会被攻击到,处于位置 \((a_i, b_i)\) 的皇后可以攻击到四条直线的棋子:\(r = a_i\)\(c = b_i\)\(r+c = a_i + b_i\)\(r-c = a_i - b_i\)

\(r = a_i\)\(c = b_i\) 的直线各自会攻击到 \(N\) 个点(包括皇后自身),\(r+c = a_i + b_i\) 的直线取决会攻击到 \(a_i + b_i - 1\) 个点(当 \(a_i + b_i \le N + 1\) 也就是直线在上半区时)或 \(2N + 1 - a_i - b_i\) 个点(当 \(a_i + b_i > N + 1\) 也就是直线在下半区时)。

\(r-c = a_i - b_i\) 的直线取决会攻击到 \(a_i - b_i + N\) (当 \(a_i - b_i \le 0\) 也就是直线在上半区时)个点或 \(N - a_i + b_i\) 个点(当 \(a_i - b_i > 0\) 也就是直线在下半区时)。

但是这样会有一些问题,有一些点会被多次计入。可以发现这些点都是直线之间产生的交点,我们可以记录下所有交点和相交次数。一共有 \(M\) 个皇后,也就是至多 \(4M\) 条直线,一个点至多被 \(4\) 条直线经过,如果有 \(2\) 个直线过 \(1\) 个点,那么两两相交的次数是 \(1\);如果有 \(3\) 个点,两两相交的次数是 \(3\)\(4\) 条直线两两相交的次数是 \(6\) 次。记录下所有交点的相交次数,最后的统一减掉就好。

参考代码
#include <iostream>
#include <map>
#include <unordered_set>
#include <utility>

using namespace std;
using LL = long long;

int main()
{
    LL n;
    int m;
    cin >> n >> m;
    unordered_set<LL> r, c, sum, diff;
    map<pair<LL, LL>, int> cnt;

    auto valid = [n](LL x) -> bool
    { return x >= 1 && x <= n; };

    for (int i = 0; i < m; i++)
    {
        LL nr, nc;
        cin >> nr >> nc;

        if (!r.count(nr))
        {
            // r = nr

            // c = cc
            for (auto cc : c)
                cnt[{nr, cc}]++;

            // r + c = s
            // c = s - nr
            for (auto s : sum)
                if (valid(s - nr))
                    cnt[{nr, s - nr}]++;

            // r - c = d
            // c = nr - d
            for (auto d : diff)
                if (valid(nr - d))
                    cnt[{nr, nr - d}]++;
        }

        if (!c.count(nc))
        {
            // c = nc

            // r = rr
            for (auto rr : r)
                cnt[{rr, nc}]++;

            // r + c = s
            // r = s - nc
            for (auto s : sum)
                if (valid(s - nc))
                    cnt[{s - nc, nc}]++;

            // r - c = d
            // r = nc + d
            for (auto d : diff)
                if (valid(nc + d))
                    cnt[{nc + d, nc}]++;
        }

        if (!sum.count(nr + nc))
        {
            // r + c = nr + nc

            // r = rr
            // c = nr + nc - rr
            for (auto rr : r)
                if (valid(nr + nc - rr))
                    cnt[{rr, nr + nc - rr}]++;

            // c = cc
            // r = nr + nc - cc
            for (auto cc : c)
                if (valid(nr + nc - cc))
                    cnt[{nr + nc - cc, cc}]++;

            // r - c = d
            // r = (nr + nc + d) / 2
            // c = (nr + nc - d) / 2
            for (auto d : diff)
                if ((nr + nc + d) % 2 == 0 && valid((nr + nc + d) / 2) && valid((nr + nc - d) / 2))
                    cnt[{(nr + nc + d) / 2, (nr + nc - d) / 2}]++;
        }

        if (!diff.count(nr - nc))
        {
            // r - c = nr - nc

            // r = rr
            // c = rr - nr + nc
            for (auto rr : r)
                if (valid(rr - nr + nc))
                    cnt[{rr, rr - nr + nc}]++;

            // c = cc
            // r = nr - nc + cc
            for (auto cc : c)
                if (valid(nr - nc + cc))
                    cnt[{nr - nc + cc, cc}]++;

            // r + c = s
            // r = (nr - nc + s) / 2
            // c = (s - nr + nc) / 2
            for (auto s : sum)
                if ((nr - nc + s) % 2 == 0 && valid((nr - nc + s) / 2) && valid((s - nr + nc) / 2))
                    cnt[{(nr - nc + s) / 2, (s - nr + nc) / 2}]++;
        }

        r.insert(nr);
        c.insert(nc);
        sum.insert(nr + nc);
        diff.insert(nr - nc);
        cnt[{nr, nc}] = 6;
    }
    LL ans = n * n - (r.size() + c.size()) * n;
    for (auto s : sum)
        ans -= s <= n + 1 ? s - 1 : 2 * n + 1 - s;
    for (auto d : diff)
        ans -= d <= 0 ? d + n : n - d;
    for (auto [p, c] : cnt)
        if (c == 1)
            ans++;
        else if (c == 3)
            ans += 2;
        else if (c == 6)
            ans += 3;
    cout << ans << endl;
    return 0;
}

G - Edit to Match

题目大意

给你 \(N(1 \le N \le 2 \times 10 ^ 5)\) 个字符串 \(S_1, S_2, ..., S_N\)。其中每个字符串都由小写字母组成且 \(\sum\limits_{i = 1} ^ N|S_i| \le \times 10 ^ 5\)

对于每个 \(k = 1, 2, ... , N\),解决以下问题:

初始的时候,你有一个字符串 \(T = S_k\),你可以执行以下两种类型的操作:

  • 花费 \(1\) 元删除 \(T\) 的最末尾字符。
  • 花费 \(1\) 元在 \(T\) 的末尾添加任意一个小写字母。

问:至少需要花费多少元,才能让 \(T\) 等于 \(S_1, S_2, ... , S_{k-1}\) 或空字符串?

解题思路

可以发现,如果 \(T\) 添加了一个字符,则后面一定不会删掉这个字符(否则,一开始就该不加这个字符)。因此最优的做法一定是先执行操作 1 删掉一部分后缀的字符,然后只执行操作 2 添加字符直到匹配。为此,我们需要知道 \(T\) 的某个前缀匹配 \(S_1, S_2, ..., S_k\) 还需要补齐多少字符,只需要用 Trie 树维护这个信息即可,如果某个前缀匹配了多个字符串,就取长度最短的那个。这个操作甚至可以在插入字符串的同时维护。

参考代码
#include <iostream>
#include <limits>
#include <string>
#include <unordered_map>

using namespace std;

struct Trie
{
    struct Node
    {
        unordered_map<char, Node *> ne;
        int dis = numeric_limits<int>::max() / 2;

        void update(int d) { dis = min(dis, d); }

        Node *at_or_new(char ch)
        {
            auto it = ne.find(ch);
            if (it == ne.end())
                it = ne.insert({ch, new Node()}).first;
            return it->second;
        }
    };

    Node *root = new Node();

    int insert(const string &s)
    {
        int n = s.size();
        int ans = n;
        Node *p = root;
        for (int i = 0; i < n; i++)
        {
            char ch = s[i];
            p = p->at_or_new(ch);
            ans = min(ans, n - i - 1 + p->dis);
            p->update(n - i - 1);
        }
        return ans;
    }
};

int main()
{
    int n;
    cin >> n;
    Trie t;
    while(n--)
    {
        string s;
        cin >> s;
        cout << t.insert(s) << endl;
    }
    return 0;
}


最后更新: 2024-12-23
创建日期: 2024-12-23