Skip to content

2022 CCPC 广州站

H. GameX

题目描述

Alice 和 Bob 在玩一个游戏。

游戏开始时,有一个整数集合 \(S = {a_1, a_2, a_3,...,a_n}\)。Alice 和 Bob 交替进行 \(k\) 轮如下操作:

每次操作,玩家可以选择一个整数 \(x\),并将 \(x\) 插入 \(S\) 中。注意,每轮中 Alice 和 Bob 都可以操作,即每轮会插入两个数。

游戏结束后,如果 \(\text{MEX}(S)\) 是偶数,则 Alice 获胜,否则 Bob 获胜。

输入

第一行包含一个正整数 \(T\) ( \(1\le T\le 10^4\) ),表示测试用例的数量。

对于每个测试用例

  • 第一行包含两个整数 \(n, k\) ( \(1\le n,k\le 2\times 10^5\) ),分别表示游戏开始前 \(S\) 的大小和回合数。
  • 下一行包含 \(n\) 个不同的自然数 \(a_1,a_2,\cdots,a_n\)\(0\le a_i\le 10^6\) )。 \(0\le a_i\le 10^6\) ),表示 \(S\)

可以保证 \(\sum n, \sum k\le 2\times 10^5\)

输出

对于每个测试用例,如果 Alice 获胜,则输出 Alice,否则输出 Bob


题解

比较简单的签到题,对于 Alice,她期望最终的 \(\text{MEX}(S)\) 是偶数,那么她的最优策略一定是在每一轮插入当前最小的未出现的奇数;同样,Bob 的最优策略是在每一轮插入当前最小的未出现的偶数。

考虑到数据范围 \(\sum k\le 2\times 10^5\),可以直接模拟这个过程,最终给出答案即可。

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

#define int long long

const int N = 1e6 + 5;

void solve() {
  int n, k;
  cin >> n >> k;
  bitset<2 * N> a;
  for (int i = 0; i < n; i++) {
    int x;
    cin >> x;
    a[x] = 1;
  }
  int alice = 1, bob = 0;
  for (int i = 0; i < k; i++) {
    while (a[alice]) {
      alice += 2;
    }
    a[alice] = 1;
    while (a[bob]) {
      bob += 2;
    }
    a[bob] = 1;
  }
  for (int i = 0; i < 2 * N; i++) {
    if (!a[i]) {
      if (i % 2 == 0) {
        cout << "Alice\n";
      } else {
        cout << "Bob\n";
      }
      return;
    }
  }
}

signed main() {
  ios::sync_with_stdio(false);
  cin.tie(nullptr);

  int t = 1;
  cin >> t;
  while (t--) {
    solve();
  }
}

L. Station of Fate

题目描述

\(n\) 人站在 \(m\) 站,排成 \(m\) 队。

你不知道哪个人在哪个车站,也不知道他们排队的顺序,因此你想计算不同方案的数量。当且仅当有一个车站的队列由不同的人组成或有不同的顺序时,两个方案才被认为是不同的。

计算不同方案的数量,对 \(998, 244, 353\) 取模。

输入

第一行包含一个整数 \(T\) ( \(1\le T\le 100\) ),表示测试用例的数量。然后是 \(T\) 个测试用例。

每个测试用例包含一行,其中包含两个整数 \(n, m\) ( \(1 \le m \le n \le 10^5\) )。

输出

针对每个测试用例,输出不同方案的数量,取模 \(998\, 244\, 353\)


题解

一个比较简单的组合数学签到。

首先考虑对 \(n\) 个人进行排列,有 \(n!\) 种方案;然后使用插板法将 \(n\) 个人分成 \(m\) 组,有 \(\binom{n-1}{m-1}\) 种方案。

所以答案是 \(n! \times \binom{n-1}{m-1} \mod 998244353\)

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

#define int long long

const int N = 1e5 + 5;
const int mod = 998244353;

int fract[N];

void init() {
  fract[0] = 1;
  for (int i = 1; i < N; i++) {
    fract[i] = (fract[i - 1] * i) % mod;
  }
}

int fastpow(int a, int b) {
  int res = 1;
  while (b) {
    if (b & 1) {
      res = (res * a) % mod;
    }
    a = (a * a) % mod;
    b >>= 1;
  }
  return res;
}

int inv(int x) {
  return fastpow(x, mod - 2);
}

void solve() {
  int n, m;
  cin >> n >> m;
  int ans = fract[n] * fract[n - 1] % mod * inv(fract[n - m]) % mod * inv(fract[m - 1]) % mod;
  cout << ans << '\n';
}

signed main() {
  ios::sync_with_stdio(false);
  cin.tie(nullptr);

  init();

  int t = 1;
  cin >> t;
  while (t--) {
    solve();
  }
}

E. Elevator

题目描述

在一幢有 \(m\) 层、编号为 \(1\)\(m\) 的大楼中,有 \(n\) 部电梯参加从 \(0\) 时刻开始的速度比赛。

\(i\) 电梯将在第 \(x_i\) 秒从 \(1\) 层出发,以每秒 \(1\) 层的速度上升。此外,除了 \(1\) 层和 \(m\)之外,每一层都有一个按钮,如果按下按钮,第一部到达该层的电梯会停止 \(1\) 秒。如果有多台电梯同时到达某一楼层,则只有编号最小的电梯才会被视为第一台电梯。现在没有按下按钮,但您可以在比赛开始前按下某些楼层的按钮。请注意,比赛开始后您不能按按钮。

现在你想知道,你是否能够通过按下按钮来操纵比赛,使 \(i\) 个电梯第一个到达 \(m\) 层,如果可以,你至少要按下多少个按钮。如果有多台电梯同时到达 \(m\) 层,则只有编号最小的电梯才会被视为第一台电梯。

输入

第一行包含两个正整数 \(n,m\) ( \(1\le n\le 5 \cdot 10^5,2\le m\le 10^9\) ),分别表示电梯和楼层的数量。

第二行包含 \(n\) 个正整数 \(x_1,\ldots,x_n\) ( \(1 \le x_i \le 10^9\) ),表示电梯的启动时间。

输出

输出 \(n\) 行。 第 \(i\) 行应包含要使第 \(i\) 个电梯第一个到达 \(m\) 层所需按下的最少按钮数。如果不可能,输出 \(-1\)


题解

首先考虑将所有电梯按照出发时间排序,如果出发时间相同,按照编号从小到大的顺序排序。

此时,排名第一的电梯无需按下任何按钮,就可以获得第一名。

对于排名第二的电梯,我们需要阻碍排名第一的电梯。如果这个电梯编号更小,那么我们要在抵达 \(m\) 层之间的路上按下足够多的按钮,使得排名第二的电梯可以反超排名第一,如果这个电梯编号更大,那么我们只需要使得两者并列向前即可。

对于排名第三的电梯,我们则需要阻碍前面两个电梯...这个过程可以以此类推下去。

设排序后的出发时间序列是 \(t_1, t_2, t_3, ..., t_n\),那么排名第 \(i\) 的电梯需要按下的按钮个数是

\[ \begin{align*} ans &= (t_i + 1 - t_1) + (t_i + 1 - t_2) + (t_i + 1 - t_3) + ... + (t_i + 1 - t_{i - 1}) - inv_i \\ &= \sum_{j = 1}^{i - 1} (t_i + 1 - t_j) - inv_i \\ &= (i - 1) \times t _i + i - 1 - presum_{i - 1} - inv_i \end{align*} \]

其中,\(presum_i\) 表示前 \(i\)电梯出发时间的前缀和,\(inv_i\) 表示前 \(i - 1\) 个电梯中,电梯序号大于第 \(i\) 个电梯序号的电梯个数,即电梯序号数组上以第 \(i\) 个元素结尾的逆序对个数。

显然,该问题无解当且仅当 \(1\) 层至 \(m\) 层的层数不足以布置 \(ans\) 多个按钮。

电梯总数 \(n\) 满足 \(1\le n\le 5 \cdot 10^5\),可以使用树状数组 \(n \log{n}\) 维护逆序对,总体复杂度 \(O(n \log{n})\), 可以通过该题。

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

#define int long long

const int N = 5e5 + 10;

int tree[N];

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

void update(int x, int v) {
  for (int i = x; i < N; i += lowbit(i)) {
    tree[i] += v;
  }
}

int query(int x) {
  int ans = 0;
  for (int i = x; i > 0; i -= lowbit(i)) {
    ans += tree[i];
  }
  return ans;
}

void solve() {
  int n, m;
  cin >> n >> m;
  vector<int> a(n);
  vector<int> idx(n);
  vector<int> ans(n);
  vector<int> presum(n + 1);
  for (int i = 0; i < n; i++) {
    cin >> a[i];
    idx[i] = i;
  }
  sort(idx.begin(), idx.end(), [&](int i, int j) { 
    if (a[i] == a[j]) {
      return i < j;
    }
    return a[i] < a[j]; 
  });
  sort(a.begin(), a.end());
  presum[0] = 0;
  for (int i = 0; i < n; i++) {
    presum[i + 1] = presum[i] + a[i];
  }
  for (int i = 0; i < n; i++) {
    update(idx[i] + 1, 1);
    int inv = i + 1 - query(idx[i] + 1);
    int pref = presum[i];
    int res = i * a[i] + i - pref - inv;
    ans[idx[i]] = (res <= m - 2 ? res : -1);
  }
  for (int i = 0; i < n; i++) {
    cout << ans[i] << "\n";
  }
}

signed main() {
  ios::sync_with_stdio(false);
  cin.tie(nullptr);

  int t = 1;
  // cin >> t;
  while (t--) {
    solve();
  }
}