题解 P11234【[CSP-S 2024] 擂台游戏】

这道题的整个题面都要读,根本简化不了。

题目描述

小 S 想要举办一场擂台游戏,如果共有 \(2^k\) 名选手参加,那么游戏分为 \(k\) 轮进行:

  • 第一轮编号为 \(1, 2\) 的选手进行一次对局,编号为 \(3, 4\) 的选手进行一次对局,以此类推,编号为 \(2^k - 1, 2^k\) 的选手进行一次对局。
  • 第二轮在只保留第一轮的胜者的前提下,相邻的两位依次进行一场对局。
  • 以此类推,第 \(k - 1\) 轮在只保留第 \(k - 2\) 轮的 \(4\) 位胜者的前提下,前两位、后两位分别进行对局,也就是所谓的半决赛。
  • \(k\) 轮即为半决赛两位胜者的决赛。

确定了游戏晋级的规则后,小 S 将比赛的规则设置为了擂台赛。具体而言,每位选手都有一个能力值 \(a_1, a_2, \dots , a_{2^k}\),能力值为 \([0,2^{31}-1]\) 之内的整数。对于每场比赛,会先抽签决定一个数 \(0/1\),我们将第 \(R\) 轮的第 \(G\) 场比赛抽到的数记为 \(d_{R,G}\)。抽到 \(0\) 则表示表示编号小的选手为擂主,抽到 \(1\) 则表示编号大的选手为擂主。擂主获胜当且仅当他的能力值 \(a\geq R\)。也就是说,游戏的胜负只取决于擂主的能力值当前比赛是第几轮的大小关系,与另一位的能力值无关

现在,小 S 先后陆续收到了 \(n\) 位选手的报名信息,他们分别告知了小 S 自己的能力值。小 S 会按照报名的先后顺序对选手进行编号为 \(1, 2, \dots, n\)。小 S 关心的是,补充尽量少的选手使总人数为 \(2\) 的整次幂,且所有选手进行一次完整的擂台游戏后,所有可能成为总冠军的选手的编号之和是多少。

形式化地,设 \(k\) 是最小的非负整数使得 \(2^k\geq n\),那么应当补充 \((2^k-n)\) 名选手,且补充的选手的能力值可以任取 \([0,2^{31}-1]\) 之内的整数。如果补充的选手有可能取胜,也应当计入答案中

当然小 S 觉得这个问题还是太简单了,所以他给了你 \(m\) 个询问 \(c_1,c_2,\dots,c_m\)。小 S 希望你帮忙对于每个 \(c_i\) 求出,在只收到前 \(c_i\) 位选手的报名信息时,这个问题的答案是多少。

输入格式

本题的测试点包含有多组测试数据。 但不同测试数据只是通过修改 \(a_1, a_2, \dots , a_n\) 得到,其他内容均保持不变,请参考以下格式。其中 \(\oplus\) 代表异或运算符,\(a \bmod b\) 代表 \(a\) 除以 \(b\) 的余数。

输入的第一行包含两个正整数 \(n, m\),表示报名的选手数量和询问的数量。

输入的第二行包含 \(n\) 个非负整数 \(a'_1,a'_2,\dots,a'_n\),这列数将用来计算真正的能力值。

输入的第三行包含 \(m\) 个正整数 \(c_1, c_2, \dots , c_m\),表示询问。

\(K\) 是使得 \(2^K \geq n\) 的最小的非负整数,接下来的 \(K\) 行当中,第 \(R\) 行包含 \(2^{K-R}\) 个数(无空格),其中第 \(G\) 个数表示第 \(R\) 轮的第 \(G\) 场比赛抽签得到的 \(d_{R,G}=0/1\)

注意,由于询问只是将人数凑齐到 \(2^k\geq c_i\),这里的 \(k\leq K\),因此你未必会用到全部的输入值。

接下来一行包含一个正整数 \(T\),表示有 \(T\) 组测试数据。

接下来共 \(T\) 行,每行描述一组数据,包含 \(4\) 个非负整数 \(X_0,X_1,X_2,X_3\),该组数据的能力值 \(a_i=a'_i \oplus X_{i\bmod 4}\),其中 \(1\leq i\leq n\)

输出格式

共输出 \(T\) 行,对于每组数据,设 \(A_i\) 为第 \(i\)\(1 \leq i \leq m\))组询问的答案,你只需要输出一行包含一个整数,表示 \((1\times A_1) \oplus (2\times A_2) \oplus \dots \oplus (m\times A_m)\) 的结果。

样例 #1

样例输入 #1

5 5
0 0 0 0 0
5 4 1 2 3
1001
10
1
4
2 1 0 0
1 2 1 0
0 2 3 1
2 2 0 1

样例输出 #1

5
19
7
1

提示

【样例 1 解释】

共有 \(T = 4\) 组数据,这里只解释第一组。\(5\) 名选手的真实能力值为 \([1, 0, 0, 2, 1]\)\(5\) 组询问分别是对长度为 \(5, 4, 1, 2, 3\) 的前缀进行的。

  1. 对于长度为 \(1\) 的前缀,由于只有 \(1\) 号一个人,因此答案为 \(1\)
  2. 对于长度为 \(2\) 的前缀,由于 \(2\) 个人已经是 \(2\) 的幂次,因此不需要进行扩充。根据抽签 \(d_{1,1} = 1\) 可知 \(2\) 号为擂主,由于 \(a_2 < 1\),因此 \(1\) 号获胜,答案为 \(1\)
  3. 对于长度为 \(3\) 的前缀,首先 \(1\) 号、\(2\) 号比赛是 \(1\) 号获胜(因为 \(d_{1,1} = 1\),故 \(2\) 号为擂主,\(a_2 < 1\)),然后虽然 \(4\) 号能力值还不知道,但 \(3\) 号、\(4\) 号比赛一定是 \(4\) 号获胜(因为 \(d_{1,2} = 0\),故 \(3\) 号为擂主,\(a_3 < 1\)),而决赛 \(1\) 号、\(4\) 号谁获胜都有可能(因为 \(d_{2,1} = 1\),故 \(4\) 号为擂主,如果 \(a_4 < 2\)\(1\) 号获胜,\(a_4 \geq 2\)\(4\) 号获胜)。综上所述,答案为 \(1 + 4 = 5\)
  4. 对于长度为 \(4\) 的前缀,我们根据上一条的分析得知,由于 \(a_4 \geq 2\) ,所以决赛获胜的是 \(4\) 号。
  5. 对于长度为 \(5\) 的前缀,可以证明,可能获胜的选手包括 \(4\) 号、\(7\) 号、\(8\) 号,答案为 \(19\)

因此,该组测试数据的答案为 \((1 \times 19) \oplus (2 \times 4) \oplus (3 \times 1) \oplus (4 \times 1) \oplus (5 \times 5) = 5\)

【样例 2】

见选手目录下的 arena/arena2.in 与 arena/arena2.ans。

这组样例满足特殊性质 A。

【样例 3】

见选手目录下的 arena/arena3.in 与 arena/arena3.ans。

这组样例满足特殊性质 B。

【样例 4】

见选手目录下的 arena/arena4.in 与 arena/arena4.ans。

【样例 5】

见选手目录下的 arena/arena5.in 与 arena/arena5.ans。

【数据范围】

对于所有测试数据,保证:\(2 \leq n, m \leq 10^5\)\(0 \leq a_i, X_j < 2^{31}\)\(1 \leq c_i \leq n\)\(1 \leq T \leq 256\)

测试点 \(T=\) \(n,m\leq\) 特殊性质 A 特殊性质 B
\(1\sim 3\) \(1\) \(8\)
\(4,5\) \(1\) \(500\)
\(6\sim 8\) \(1\) \(500\)
\(9,10\) \(1\) \(5000\)
\(11,12\) \(1\) \(10^5\)
\(13\sim 15\) \(1\) \(10^5\)
\(16,17\) \(4\) \(10^5\)
\(18,19\) \(16\) \(10^5\)
\(20,21\) \(64\) \(10^5\)
\(22,23\) \(128\) \(10^5\)
\(24,25\) \(256\) \(10^5\)

特殊性质 A:保证询问的 \(c_i\) 均为 \(2\) 的幂次。

特殊性质 B:保证所有的 \(d_{R,G} = 0\)

solution 84

枚举最后补全到 \(2^k\) 个人,显然不会影响复杂度。

这个游戏就是一个二叉树结构,直接建类似 zkw 线段树的结构方便考虑。考虑第 \(i\) 个人怎么赢,它到根的路径上,是擂主的时候必须赢(也就是其能力值要不小于某个可以用 \(d\) 预处理的数),不是擂主是时候要求擂主能输。由于有伪人的存在(由于有人的能力值不确定),我们不一定能知道擂主能不能输。先写一个带 \(\log\) 的东西,记 \(sum_p\) 表示结点 \(p\) 的子树中可能赢的人的编号,\(up_p\) (一个集合)表示结点 \(p\) 的赢家的可能的能力值(显然可以对 \(17\) chkmin)。当一个伪人的能力值塌陷时(当一个人的能力值确定时),他到根的路径的 \(sum, up\) 都被更改。如果我们提前将某些不可能赢的人(指其做某一层擂主的时候会输)的人提前枪毙,使其不计入 \(sum\),并记 \(R\)\(p\) 所在的轮数,\(lc\) 是擂主方的儿子,\(rc\)\(lc\) 的兄弟,那么:

  • \(up_p=\{x\in up_{lc}|x\geq R\}\) 表示擂主方赢的部分。如果 \(\exists x\in up_{lc_p}\) 那么擂主可以输掉,\(up_p\) 额外 \(\cup\)\(up_{rc}\)
  • \(sum_p=sum_{lc}\) 表示赢的擂主的编号和(可能输的擂主已经枪毙)。如果 \(\exists x\in up_{lc_p}\) 那么擂主可以输掉,\(sum_p\) 额外 \(+\)\(sum_{rc}\)

维护之,复杂度 \(O(Tn\log n)\)。实现要好一点。关键在于我们用一个 \(up\) 去除了伪人带来的不确定性,充要地刻画了每个人赢的条件,并在每次修改时维护。

solution 100

为了优化,提出重要性质:\(up_p\) 要么 \(\supseteq[R, +\infty)\) 要么 \(|up_p|=1\),前者称为伪人赢家,后者称为真人赢家,且在同一个 \(p\) 上的赢家一定先是伪人再是真人。(此处 \(R\)\(p\) 所在轮数,而 \(p\) 作擂主的时候 \(R\) 取其父亲的 \(R\),要加一)

归纳证明:归纳基石显然。

  • 擂主是伪人,则结果还是伪人,但其实擂主的 \(up\) 存在 \(\leq R\) 部分,但不影响结果是伪的。
  • 擂主是真人,若真人的能力值 \(\geq R+1\) 则结果是他,否则结果要么是另一个真人要么是伪人,伪人上来之后伪人性质继续保持。

“在同一个 \(p\) 上的赢者一定先是伪人再是真人”的证明也是直接归纳,太直接了,不写了。证毕。

可以对每个结点处理出 \(tim_p\) 表示这个点的赢家第一次是真人的时间,\(a_p\) 表示这个真人的能力值。发现这个东西可以线性处理了。

计算了 \(tim_p\) 之后,观察 \(sum_p\) 的转移方式,就是真人第一次在这个擂台上获胜的时候,另一方就再也不可能赢了。维护每个人到根的最小的一个时间戳,表示这个时间戳往后他就不可能被转移上去,也就是不能再赢了。如此就发现每个人能赢的时间是一段连续的区间,差分计算答案即可。注意要去掉这个人塌陷为真人之后做某一层擂主的时候会输造成的错误贡献,这个东西可以 \(O(n\log n)\) 预处理以 \(O(1)\) 判断。复杂度 \(O(n\log n+Tn)\)

ref:https://www.luogu.com.cn/article/mmehkagt

code

84
#include <bits/stdc++.h>
using namespace std;
#ifdef LOCAL
#define debug(...) fprintf(stderr, ##__VA_ARGS__)
#else
#define debug(...) void(0)
#define endl "\n"
#endif
using LL = long long;
int n, m, a0[200010], cq[200010], up[400010], K;
bool rev[400010];
LL ans[200010], sum[400010];
void maintain(int p) {
  int lc = p << 1, rc = p << 1 | 1, R = K - __lg(p);
  if (rev[p]) swap(lc, rc);
  int lo = up[lc] & ((1 << R) - 1);
  up[p] = up[lc] - lo;
  sum[p] = sum[lc];
  if (lo) up[p] |= up[rc];
  if (lo) sum[p] += sum[rc];
}
int mian() {
  int X[4];
  for (int i = 0; i < 4; i++) cin >> X[i];
  for (int i = 1; i < 1 << (K + 1); i++) up[i] = (1 << 18) - 1;
  for (int i = 0; i < 1 << K; i++) sum[(1 << K) + i] = i + 1;
  for (int i = (1 << K) - 1; i >= 1; i--) sum[i] = sum[i << 1] + sum[i << 1 | 1];
  int ansp = (1 << K);
  for (int i = 0; i < n; i++) {
    int p = (1 << K) + i, v = a0[i] ^ X[(i + 1) % 4];
    up[p] = 1 << min(v, 17);
    for (int q = p; q > ansp && sum[p] && v < 17; q >>= 1) if ((q & 1) == rev[q >> 1] && v < K - __lg(q >> 1)) sum[p] = 0;
    while ((p >>= 1) >= ansp) maintain(p);
    ans[i + 1] = sum[ansp];
#ifdef LOCAL
    if ((i & (i + 1)) == 0) debug("ans[%d] = %d\n", i + 1, ans[i + 1]);
    //  for (int i = 1; i < 1 << (K + 1); i++) debug("up[%d] = %s\n", i, bitset<18>(up[i]).to_string().c_str());
    //  for (int i = 1; i < 1 << (K + 1); i++) debug("sum[%d] = %lld\n", i, sum[i]);
#endif
    if ((i & (i + 1)) == 0) {
      ansp >>= 1;
      if (!rev[ansp]) {
        int R = K - __lg(ansp);
        debug("R = %d\n", R);
        for (int j = 0; j <= i; j++) {
          int v = a0[j] ^ X[(j + 1) % 4];
          if (v < R) sum[(1 << K) + j] = 0;
        }
        for (int j = R - 1; j >= 0; j--) for (int i = ansp << j; i < ((ansp + 1) << j) - (j ? 1 << (j - 1): 0); i++) maintain(i);
      }
    }
  }
  LL fans = 0;
  for (int i = 1; i <= m; i++) fans ^= 1ll * i * ans[cq[i]];
  cout << fans << endl;
  return 0;
}
int main() {
#ifndef LOCAL
#ifdef NF
  freopen("arena.in", "r", stdin);
  freopen("arena.out", "w", stdout);
#endif
  cin.tie(nullptr)->sync_with_stdio(false);
#endif
  cin >> n >> m;
  while (1 << K < n) ++K;
  for (int i = 0; i < n; i++) cin >> a0[i];
  for (int i = 1; i <= m; i++) cin >> cq[i];
  for (int k = 1; k <= K; k++) {
    static char buf[200010];
    cin >> buf;
    int st = 1 << (K - k);
    for (int i = 0; i < st; i++) rev[st + i] = buf[i] == '1';
  }
  int t;
  cin >> t;
  while (t--) mian();
  return 0;
}
100
#include <bits/stdc++.h>
using namespace std;
#ifdef LOCAL
#define debug(...) fprintf(stderr, ##__VA_ARGS__)
#else
#define endl "\n"
#define debug(...) void(0)
#endif
#define lg __lg
using LL = long long;
int n, m, cq[100010], K, a0[200010], tim[400010], a[400010], atm[400010], pos[200010][18];
bool rev[400010];
LL ans[400010];
int mian() {
  int X[4];
  for (int &x : X) cin >> x;
  for (int i = 0; i < n; i++) a[1 << K | i] = a0[i] ^ X[(i + 1) & 3], tim[1 << K | i] = i, ans[i] = 0;
  for (int i = n; i < 1 << K; i++) tim[1 << K | i] = i;
  ans[0] = 1, ans[1] = -1;
  for (int k = 1; k <= K; k++) {
    debug("k = %d\n", k);
    for (int i = 1; i <= k; i++) {
      for (int j = 0; j < 1 << (k - i); j++) {
        int p = (1 << (K - i)) + j;
        int lc = p << 1 | rev[p], rc = p << 1 | !rev[p], R = K - lg(p);
        debug("p = %d, R = %d\n", p, R);
        if (a[lc] >= R) tim[p] = tim[lc], a[p] = a[lc], debug("?");
        else tim[p] = max(tim[lc], tim[rc]), a[p] = a[rc], debug("!");
        debug("rev[%d] = %d, a[%d] = %d, tim[%d] = %d\n", p, rev[p], p, a[p], p, tim[p]);
      }
    }
    atm[1 << (K - k)] = min(n, 1 << k);
    for (int i = k; i >= 1; i--) {
      for (int j = 0; j < 1 << (k - i); j++) {
        int p = (1 << (K - i)) + j;
        debug("p = %d\n", p);
        debug("atm[%d] = %d\n", p, atm[p]);
        int lc = p << 1 | rev[p], rc = p << 1 | !rev[p], R = K - lg(p);
        atm[lc] = atm[p];
        if (a[lc] >= R) atm[rc] = min(atm[p], tim[p]);
        else atm[rc] = atm[p];
      }
    }
    int st = 1 << (k - 1);
    debug("st = %d\n", st);
    for (int i = 0; i < 1 << k; i++) {
      ans[st] += i + 1;
      int ed = max(st, atm[1 << K | i]);
      if (a[1 << K | i] < pos[i][k]) debug("!!"), ed = max(st, min(ed, i));
      debug("a[%d] = %d, atm[%d] = %d, pos[%d][%d] = %d\n", i, a[1 << K | i], i, atm[1 << K | i], i, k, pos[i][k]);
      if (st < ed) debug("%d -> ans[%d, %d)\n", i, st, ed);
      ans[ed] -= i + 1;
    }
  }
  for (int i = 1; i < n; i++) ans[i] += ans[i - 1];
  LL fans = 0;
  for (int i = 1; i <= m; i++) debug("ans[%d] = %lld\n", cq[i], ans[cq[i] - 1]), fans ^= i * ans[cq[i] - 1];
  cout << fans << endl;
  return 0;
}
int main() {
#ifndef LOCAL
  cin.tie(nullptr)->sync_with_stdio(false);
#endif
  cin >> n >> m;
  for (int i = 0; i < n; i++) cin >> a0[i];
  for (int i = 1; i <= m; i++) cin >> cq[i];
  while (1 << K < n) ++K;
  for (int i = K - 1; i >= 0; i--) {
    static char buf[200010];
    cin >> buf;
    for (int j = 0; j < 1 << i; j++) rev[1 << i | j] = buf[j] == '1';
  }
  for (int i = 0; i < n; i++) {
    int p = 1 << K | i;
    int lst = 0;
    for (int k = 1; k <= K; k++) {
      if (rev[p >> 1] == (p & 1)) lst = k;
      p >>= 1;
      pos[i][k] = lst;
    }
  }
  int t;
  cin >> t;
  memset(a, 0, sizeof a);
  while (t--) mian();
  return 0;
}
posted @ 2024-10-28 17:34  caijianhong  阅读(214)  评论(0编辑  收藏  举报