跳转至

ABC380(A-G)

A - 123233

题目大意

给你一个 \(6\) 位数 \(N\),判断 \(N\) 是否满足以下所有条件:

  • \(N\) 的数位中,\(1\) 恰好出现了 \(1\) 次。
  • \(N\) 的数位中,\(2\) 恰好出现了 \(2\) 次。
  • \(N\) 的数位中,\(3\) 恰好出现了 \(3\) 次。
参考代码
#include <iostream>
#include <string>
#include <vector>

using namespace std;

int main()
{
    string n;
    cin >> n;
    vector<int> cnt(10);
    for(auto ch : n)
        cnt[ch - '0']++;
    if(cnt[1] == 1 && cnt[2] == 2 && cnt[3] == 3)
        cout << "Yes" << endl;
    else
        cout << "No" << endl;
    return 0;
}

B - Hurdle Parsing

题目大意

有一个长度为 \(N\) 的序列 \(A = (A_1, A_2, ..., A_N)\),某人根据序列 \(A\) 生成了一个字符串 \(S\),生成的规则如下:

  • 初始的时候,\(S\) 只有一个字符 |
  • 接下来,对于每个 \(i = 1, 2, ..., N\),执行以下操作:
    • \(S\) 的末尾添加 \(A_i\)-
    • 然后,在 \(S\) 的末尾添加一个 |

现在,给你一个生成后的字符串 \(S\),你需要将其还原成序列 \(A\)

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

using namespace std;

int main()
{
    string s;
    cin >> s;
    int n = s.size();
    int cnt = 0;
    for(int i = 1; i < n; i++)
    {
        if(s[i] == '|')
        {
            cout << cnt << ' ';
            cnt = 0;
        }
        else
            cnt++;
    }
    return 0;
}

C - Move Segment

题目大意

给你一个长度为 \(N(1 \le N \le 5 \times 10 ^ 5)\) 的字符串 \(S\)\(S\)01 组成。多个连续的 1 组成 1-block,例如 010011100011001 字符串一共有 \(4\)1-block,即 0[1]00[111]000[11]00[1](此处中括号 [] 仅仅是用来标记每个 1-block )。

给你一个数字 \(K(2 \le K)\),你需要将第 \(K\)1-block,挪到第 \(K-1\)1-block 的后面,其他位置保持不变。输入数据保证字符串 \(S\) 一定有至少 \(K\)1-block。输出挪动后的字符串 \(S\)

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

using namespace std;

int main()
{
    int n, k;
    cin >> n >> k;
    string s;
    cin >> s;
    int i = 0, cnt = 0;
    while(cnt < k - 1)
    {
        if(s[i] == '1')
        {
            while(s[i] == '1')
                i++;
            cnt++;
        }
        else
            i++;
    }
    for(int j = i; j < n; j++)
    {
        if(s[j] == '1')
        {
            while(j < n && s[j] == '1')
            {
                s[i++] = '1';
                j++;
            }
            while(i < j)
                s[i++] = '0';
            break;
        }
    }
    cout << s << endl;
    return 0;
}

D - Strange Mirroring

题目大意

给你一个字符串 \(S(1 \le |S| \le 2 \times 10 ^ 5)\)\(S\) 由小写字母组成。

你需要在 \(S\) 上执行以下操作 \(10^{100}\) 次:

  • 首先,创建一份字符串 \(S\) 的拷贝 \(T\),即令 \(T = S\),然后将 \(T\) 的所有大写字母变小写,所有小写字母变大写。
  • 然后,令 \(S = S + T\),其中 \(+\) 表示将两个字符串拼接。

在执行完操作后,回答 \(Q(1 \le Q \le 2 \times 10 ^ 5)\) 个询问,每个询问给你一个 \(K_i(1 \le K_i \le 10 ^ {18})\),你需要回答 \(S\) 的第 \(K_i\) 个字符是什么。

解题思路

每一次操作会让 \(S\) 的长度翻倍,所以长度一定是形如 \(2^j|S|\) 的,因为 \(K_i \le 10 ^ {18}\),可以处理出这个范围内所有可能的长度,即 \(p[j]\) 表示操作 \(j\) 次后 \(S\) 的长度。

对于一个 \(K_i\),一定存在一个最大的 \(j\) 使得 \(p[j] \le K_i\),则 \(K_i\) 相当于是在后半段的上,对应前半段 \(K_i - p[j]\) 位置大小写反转后字符,按这个流程处理下去直到 \(K_i \le |S|\) 即可。

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

using namespace std;
using LL = long long;

int main()
{
    string s;
    cin >> s;
    LL n = s.size();
    int q;
    cin >> q;
    vector<LL> p;
    for(LL x = n; x < 1E18; x <<= 1)
        p.push_back(x);
    int len = p.size();
    while (q--)
    {
        LL k;
        cin >> k;
        k--;
        bool flag = false;
        for(int i = len - 1; i >= 0; i--)
            if(k >= p[i])
            {
                flag = !flag;
                k -= p[i];
            }
        char ans = s[k];
        if(flag)
            ans = islower(ans) ? toupper(ans) : tolower(ans);
        cout << ans << ' ';
    }
    return 0;
}

E - 1D Bucket Tool

题目大意

\(N(1 \le 5 \times 10 ^ 5)\) 个格子,第 \(i\) 个格子的初始颜色是 \(i\)。两个格子如果位置相邻且颜色相同的则连通。

给你 \(Q(1 \le Q \le 2 \times 10 ^ 5)\) 个询问,询问有两种类型:

  • 1 x c:将第 \(x\) 个格子所在的连通块都染成颜色 \(c\)
  • 2 c: 查询颜色是 \(c\) 的格子的数量。
解题思路

因为染色是一块一块染的,所以显然应该用并查集维护。至于操作 2,再开一个哈希表记录每种颜色的数量就好。

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

using namespace std;

class DSU
{
public:
    explicit DSU(int n) : parent_or_size(n+1, -1), left(n+1), right(n+1), color(n+1)
    {
        for (int i = 0; i <= n; i++)
            left[i] = right[i] = color[i] = i;
    }

    void merge(int a, int b)
    {
        int x = leader(a), y = leader(b);
        if (x == y)
            return;
        if (-parent_or_size[x] < -parent_or_size[y])
            std::swap(x, y);
        left[x] = min(left[x], left[y]);
        right[x] = max(right[x], right[y]);
        parent_or_size[x] += parent_or_size[y];
        parent_or_size[y] = x;
    }

    int leader(int a) { return parent_or_size[a] < 0 ? a : parent_or_size[a] = leader(parent_or_size[a]); }

    bool same(int a, int b) { return leader(a) == leader(b); }

    int size(int a) { return -parent_or_size[leader(a)]; }

    int get_left(int a) { return left[leader(a)]; }
    int get_right(int a) { return right[leader(a)]; }
    int get_color(int a) { return color[leader(a)]; }
    void set_color(int a, int c) { color[leader(a)] = c; }

private:
    vector<int> parent_or_size;
    vector<int> left, right, color;
};

int main()
{
    int n, q;
    cin >> n >> q;
    vector<int> cnt(n + 1, 1);
    DSU dsu(n);
    while (q--)
    {
        int op;
        cin >> op;
        if (op == 1)
        {
            int x, c;
            cin >> x >> c;
            x = dsu.leader(x);
            int s = dsu.size(x);
            cnt[dsu.get_color(x)] -= s;
            cnt[c] += s;
            dsu.set_color(x, c);
            int l = dsu.get_left(x);
            int r = dsu.get_right(x);
            if (l - 1 && dsu.get_color(l - 1) == c)
                dsu.merge(l - 1, x);
            if (r + 1 <= n && dsu.get_color(r + 1) == c)
                dsu.merge(r + 1, x);
        }
        else
        {
            int c;
            cin >> c;
            cout << cnt[c] << endl;
        }
    }
    return 0;
}

F - Exchange Game

题目大意

Takahashi 和 Aoki 在玩一个卡牌游戏,初始的时候,Takahashi 手上有 \(N\) 张牌,Aoki 手上有 \(M\) 张牌,桌子上有 \(L\) 张牌。其中 \(1 \le N + M + L \le 12\),每一张卡牌上都写了一个 \([1, 10^9]\) 范围内的数字。

双方轮流行动,由 Takahashi 先手,轮到某人行动时,执行以下操作:

  • 从手中挑选任意一张牌打出,设打出的牌数字是 \(X\),然后,可以从桌子上拿一张数字小于 \(X\) 的牌,加入到自己的手牌中。

双方都知道对方的手牌有哪些,也知道桌子上有哪些牌。问:谁是必胜方?

解题思路

显然是一个简单博弈论套 DP,考虑如何编码。首先将所有牌排序之后离散化,最多 \(12\) 张牌,如果记录每一张牌当前属于谁,一共有 \(3\) 种状态,需要 \(2\) 个 bit,状态数就是 \(4^{12} = 2^{24}\) 个。如果记录每个人有哪些牌,还需要额外引入一些 bit 记录每个人手牌区的大小,状态数要多一些,所以用前一种方法就好。

参考代码
#include <functional>
#include <iostream>
#include <map>
#include <unordered_map>
#include <vector>

using namespace std;

int main()
{
    int n, m, l;
    cin >> n >> m >> l;
    int len = n + m + l;
    vector<int> a(n), b(m), c(l);
    vector<int> x;
    map<int, vector<int>, greater<int>> mp;
    for (int i = 0; i < n; i++)
    {
        cin >> a[i];
        mp[a[i]].push_back(0);
    }
    for (int i = 0; i < m; i++)
    {
        cin >> b[i];
        mp[b[i]].push_back(1);
    }
    for (int i = 0; i < l; i++)
    {
        cin >> c[i];
        mp[c[i]].push_back(2);
    }
    int idx = 0, s = 0;
    vector<vector<bool>> greater(len, vector<bool>(len));
    for (auto [x, v] : mp)
    {
        int k = idx;
        for (auto i : v)
        {
            for (int j = 0; j < k; j++)
                greater[idx][j] = true;
            idx++;
            s = s << 2 | i;
        }
    }
    unordered_map<int, bool> dp[2];

    auto get_state = [](int s, int i) -> int
    { return s >> (2 * i) & 0b11; };

    auto set_state = [](int s, int i, int x) -> int
    { return (s & ~(0b11 << (2 * i))) | (x << (2 * i)); };

    auto DP = [&dp, &greater, &get_state, &set_state, len](auto &self, int player, int s) -> bool
    {
        auto it = dp[player].find(s);
        if (it != dp[player].end())
            return it->second;
        bool win = false;
        for (int i = 0; i < len; i++)
        {
            int belong = get_state(s, i);
            if (belong != player)
                continue;
            int ns = set_state(s, i, 2);
            if(!self(self, player ^ 1, ns))
            {
                win = true;
                break;
            }
            for(int j = 0; j < i; j++)
            {
                if(!greater[i][j])
                    break;
                if(get_state(s, j) == 2 && !self(self, player ^ 1, set_state(ns, j, player)))
                {
                    win = true;
                    break;
                }
            }
        }
        dp[player][s] = win;
        return win;
    };
    cout << (DP(DP, 0, s) ? "Takahashi" : "Aoki") << endl;
    return 0;
}

G - Another Shuffle Window

题目大意

给你一个 \(N(1 \le N \le 2 \times 10 ^ 5)\) 的排列 \(P = (P_1, P_2, ..., P_N)\) 和一个整数 \(K(1 \le K \le N)\),你将执行一次以下操作:

  • 首先,等概率的从 \([1, N-K+1]\) 中随机选择一个数字 \(i\)
  • 然后,将 \(P_i, P_{i+1}, ..., P_{i+K-1}\) 随机打乱顺序。

问:在操作一次之后,排列 \(P\) 的逆序对的数学期望是多少?答案对 \(998244353\) 取模。

解题思路

首先需要推一个小结论:对于一个长度为 \(K\) 且任意两个数字都不相同的序列,将其随机打乱之后,逆序对数的数学期望是 \(\frac{K(K-1)}{4}\)

证明:长度为 \(K\) 的序列一共有 \(\frac{K(K-1)}{2}\) 对数,对于任意的数对,要么前者大于后者,要么后者大于前者,两者概率相等,因此逆序对数的数学期望是 \(\frac{K(K-1)}{4}\)

下面需要将序列进行切分,对于 \(i = a\),我们可以将序列分成 \([1, a-1], [a, a+K-1], [a+K, N]\) 三段

下面称 \([1, a-1]\)\(X\) 段,\([a, a+K-1]\)\(Y\) 段,\([a+K, N]\)\(Z\) 段。一共有 6 处位置会产生逆序对:

  1. \(X\) 内部的逆序对。
  2. \(Y\) 内部的逆序对。
  3. \(Z\) 内部的逆序对。
  4. \(X\)\(Y\) 产生的逆序对。
  5. \(X\)\(Z\) 产生的逆序对。
  6. \(Y\)\(Z\) 产生的逆序对。

假设 \(i = a\) 位置的逆序对已经求出,考虑从 \(i = a\) 如何转移到 \(i = a+1\),这涉及到两个元素归属的变化,首先 \(P_a\) 脱离 \(Y\) 加入 \(X\),然后 \(P_{a+K}\) 脱离 \(Z\) 加入 \(Y\)

首先可以发现,对于逆序对 2( \(Y\) 内部)来说,一加一减不会影响序列的大小,又因为 \(Y\) 会再打乱一次,所以根据刚刚推理的结论,\(Y\) 内部的逆序对的数学期望始终不变,即 \(\frac{K(K-1)}{4}\)

对于其他情况,我们将转移切分成四个事件:\(P_a\) 脱离 \(Y\)\(P_a\) 加入 \(X\)\(P_{a+K}\) 脱离 \(Z\)\(P_{a+K}\) 加入 \(Y\),开始分类讨论:

  1. \(P_a\) 脱离 \(Y\),除了逆序对 2(\(Y\) 内部) 已经固定之外,还会影响逆序对 4(\(X\)\(Y\)) 和 逆序对 6(\(Y\)\(Z\)),我们需要减去 \(P_a\) 的贡献。对于逆序对 4,需要减掉 \(X\) 中比 \(P_a\) 大的数字的数量;对于逆序对 6, 需要减掉 \(Z\) 中比 \(P_a\) 小的数字的数量。
  2. \(P_a\) 加入 \(X\),会影响逆序对 1(\(X\) 内部)、4(\(X\)\(Y\))、5(\(X\)\(Z\)),我们需要加上 \(P_a\) 的贡献。对于逆序对 1 ,需要加上 \(X\) 中比 \(P_a\) 大的数字的数量,这可以和 \(P_a\) 脱离 \(Y\) 中减掉的部分抵消;对于逆序对 4,需要加上 \(Y\) 中比 \(P_a\) 小的数字的数量;对于逆序对 5,需要加上 \(Z\) 中比 \(P_a\) 小的数字的数量,这也可以和 \(P_a\) 脱离 \(Y\) 中减掉的部分抵消。
  3. \(P_{a+K}\) 脱离 \(Z\),会影响逆序对 3(\(Z\) 内部)、5(\(X\)\(Z\))、6(\(Y\)\(Z\)),我们需要减去 \(P_{a+K}\) 的贡献。对于逆序对 3 ,需要减掉 \(Z\) 中比 \(P_{a+K}\) 小的数字的数量;对于逆序对 5,需要减掉 \(X\) 中比 \(P_{a+K}\) 大的数字的数量;对于逆序对 6,需要减掉 \(Y\) 中比 \(P_{a+K}\) 大的数字的数量。
  4. \(P_{a+K}\) 加入 \(Y\),除了逆序对 2(\(Y\) 内部) 已经固定之外,还会影响逆序对 4(\(X\)\(Y\)) 和 逆序对 6(\(Y\)\(Z\))。对于逆序对 4,需要加上 \(X\) 中比 \(P_{a+K}\) 大的数字的数量,这可以和 \(P_{a+K}\) 脱离 \(Z\) 中减掉的部分抵消;对于逆序对 6,需要加上 \(Z\) 中比 \(P_{a+K}\) 小的数字的数量,这可以和 \(P_{a+K}\) 脱离 \(Z\) 中减掉的部分抵消;

综上,1 和 2 抵消了一部分,但还需要加上 \(Y\) 中比 \(P_a\) 小的数字的数量,减掉 \(Y\) 中比 \(P_{a+K}\) 大的数字的数量。因此只需要针对 \(Y\) 的部分维护一个树状数组即可。此外,初始的时候,需要求一次 \(i = 1\) 的答案,所以还需要求一遍逆序对 3 和逆序对 6,所以还需要求出 \(Z\) 的树状数组。

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

using namespace std;
using LL = long long;

const LL MOD = 998244353;

struct FenwickTree
{
    int n;
    vector<int> t;

    FenwickTree(int n) : n(n), t(n + 1) {}

    int lowbit(int x) const { return x & -x; }

    int prefix(int x) const
    {
        int sum = 0;
        for (; x; x -= lowbit(x))
            sum += t[x];
        return sum;
    }

    int query(int l, int r) const { return prefix(r) - prefix(l - 1); }

    void add(int x, int k)
    {
        for (; x <= n; x += lowbit(x))
            t[x] += k;
    }
};

pair<LL, LL> exgcd(LL a, LL b)
{
    if (b == 0)
        return {1, 0};
    auto [x, y] = exgcd(b, a % b);
    return {y, x - a / b * y};
}

LL inv(LL a)
{
    auto [x, y] = exgcd(a, MOD);
    return (x + MOD) % MOD;
}

int main()
{
    int n;
    LL k;
    cin >> n >> k;
    vector<int> p(n);
    for (int i = 0; i < n; i++)
        cin >> p[i];
    FenwickTree mid(n), suf(n);
    LL sum = 0;
    for (int i = 0; i < k - 1; i++)
        mid.add(p[i], 1);
    for (int i = k - 1; i < n; i++)
    {
        sum += suf.query(p[i] + 1, n);
        sum += mid.query(p[i] + 1, n);
        suf.add(p[i], 1);
    }
    LL fix_cnt = k * (k - 1) % MOD * inv(4) % MOD;
    LL frac = inv(n - k + 1);
    LL ans = 0;
    for (int i = 0, j = k - 1; j < n; i++, j++)
    {
        sum -= mid.query(p[j]+1, n);
        mid.add(p[j], 1);
        ans = (ans + fix_cnt + sum) % MOD;
        sum += mid.query(1, p[i]-1);
        mid.add(p[i], -1);
    }
    ans = ans * frac % MOD;
    cout << ans << endl;
    return 0;
}


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