loj 4337 「CSP-S 2024」擂台游戏 - 动态规划

题目传送门

  传送门

Part 1 

  先考虑 $c_* = n$ 的情形,先考虑怎么判定每个选手有无可能成为赢家。

  比赛过程可以看成一棵满二叉树。每个选手是树的一个叶节点。

  当前选手 $i$ 依次进行的比赛可以分为两种:$i$ 作为擂主和兄弟节点 $q$ 的子树中的赢家作为擂主。

  • 判定前者只需要检查 $i$ 作为擂主时的最大轮次和 $a_i$ 的大小(如果 $i$ 为自由选手,那么将 $a_i$ 视为 $\infty$ 即可)
  • 判定后者考虑将子树内所有的自由节点设为当前轮次 - 1(即在子树内作为擂主时必定晋级,但是在当前点时必定不能晋级),然后判断子树根的值和当前轮次的大小。

  对于后面一部分判断,其实在被考虑子树中,自由节点座位擂主时必定晋级。因此我们将所有自由选手视为 $\infty$,先预处理出每个点最终的赢家的权值(自由选手或者非自由选手的确定值)。对于每个查询,依次考虑每个选手,暴力跳祖先去检查是否能获胜。

  总时间复杂度 $O(Tnm\log n)$,期望得分 40 ~ 48 分。

Part 2

  我们希望不要每个询问都去暴力枚举每个点计算答案。考虑对于每种高度的树单独处理出所有询问答案。(注意 $1 + 2 + \cdots + 2^{k} = 2^{k+1} - 1 = O(n)$)

  对于前缀询问可以看作按顺序枚举每个选手,依次将每个选手从自由选手变为非自由选手。考虑改变时,从叶节点到根节点依次去更新树上每个点的赢家信息:

  • 如果当前点赢家原先是自由选手,那么它可能变为非自由选手。
  • 如果当前点赢家原先是非自由选手,也就是此时擂主已经是之前的非自由选手,结果依旧不会改变。

  也就是意味着当一个点的赢家变为非自由选手时,它便确定了。当一个点确定时,一半的子树不能成为赢家。当它未确定的时候有两种情况:

  1. 擂主子树根确定但不能成为赢家,另一子树根未确定
  2. 擂主子树根为非自由选手。

  注意到第一种情况中当前选手作为擂主子树,这种 case 不属于当前选手作为比赛中非擂主的情况。因此我们将非擂主的判定修改为:选手的所有祖先节点满足两个条件之一:节点非确定或节点确定且赢家为选手所在子树。

  因此每个点相当会在某个时刻 $f_i$ 开始,ban 掉一半的子树。考虑预处理出每个点开始被 ban 掉的时间 $g_i$(在时刻 $i$,$i$ 号选手变为非自由选手),那么每个选手在时刻 $t$ 能成为赢家的条件是:

  1. $g_i > t$
  2. $i$ 为自由选手或 $a_i$ 满足作为擂主子树时胜利的条件(即大于某个最小能力值要求)。

  对于作为擂主子树时胜利的条件,可以用一个简单的 dp 去预处理。详细讲一下 $f, g$ 怎么来转移。

  • 对于 $f$:考虑点 $p$,记擂主子树和非擂主子树分别为 $u, v$,如果擂主子树未确定,那么一定未确定,当擂主子树确定时:
    • 如果擂主子树不能成为赢家,那么 $f_p = \max\{f_{u}, f_v\}$
    • 否则有,$f_p = f_u$
    当 $p$ 确定的时候,容易求出此时允许成为赢家的子树。
  • 对于 $g$:先处理出 $f$。自顶向下考虑,设 $p$ 的父节点为 $q$。
    • 如果 $q$ 确定时,允许 $p$ 成为赢家,那么 $g_p = g_q$
    • 否则有 $g_p = \min\{g_q, f_q\}$

  对于每个选手能成为赢家的时间一个区间,差分后前缀和即可。

  总时间复杂度 $O(T(n + m))$,期望得分 100 分。

Code

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

#define ll long long

template <typename T>
bool vmin(T& a, T b) {
  return a > b ? (a = b) : false;
}

const int N = 1 << 17;

int T;
int n, m;
int n2, L;
int _a[N], a[N], qry[N];
bool d[N];

char buf[N];

int f[N << 1];      // the timestamp when the winner of the i-th node is determined
int g[N << 1];      // the earliest timestamp when the i-th node can NOT reach the root
int miv[N << 1];    // the minimum capacity needed to reach the root of the tree
int tr[N << 1];     // the determined capacity value of the i-th node when its value is determined
int type[N << 1];   // the ID of the winner child of the i-th node when its value is determined
ll result[N], sum[N + 4];

void solve(int layer) {
  int q2 = 1 << layer;
  int root = n2 >> layer;
  g[root] = q2;
  miv[root] = 0;
  for (int i = 1; i <= layer; i++) {
    int layer_width = 1 << i;
    int layer_start = n2 >> (layer - i);
    for (int j = 0; j < layer_width; j++) {
      int p = layer_start + j;
      int q = p >> 1;
      int rd = layer - i + 1;
      int zson_q = q << 1 | d[q];
      g[p] = (p == type[q] ? g[q] : min(g[q], f[q]));
      miv[p] = (p == zson_q ? max(miv[q], rd) : miv[q]);
    }
  }
  fill(sum, sum + q2 + 1, 0);
  for (int i = 0; i < q2; i++) {
    // the conditions that make the i-th competitor become a winner:
    // 1. the i-th competitor can reach the root.
    // 2. the capacity of the i-th competitor is arbitrary, or the capacity of the i-th competitor is not less than miv[n2 + i]
    int R = g[n2 + i];
    int r = (a[i] >= miv[n2 + i] ? R : min(R, i));
    // cerr << r << " ";
    sum[0] += (i + 1);
    sum[r] -= (i + 1);
  }
  // cerr << '\n';
  for (int i = 1; i <= q2; i++) {
    sum[i] += sum[i - 1];
    result[i - 1] = sum[i - 1];
  }
}

void solve() {
  static int X[4];
  for (int i = 0; i < 4; i++) {
    scanf("%d", X + i);
  }
  for (int i = 0; i < n; i++) {
    a[i] = _a[i] ^ X[(i + 1) & 3];
  }
  // calculate f
  for (int i = 0; i < n2; i++) {
    f[n2 + i] = i;
    tr[n2 + i] = a[i];
  }
  for (int i = L; i--; ) {
    int layer_width = 1 << i;
    int layer_start = n2 >> (L - i);
    for (int j = 0; j < layer_width; j++) {
      int p = layer_start + j;
      int zson = p << 1 | d[p];
      int rd = (L - i);
      f[p] = (tr[zson] >= rd) ? f[zson] : max(f[zson], f[zson ^ 1]);
      tr[p] = (tr[zson] >= rd) ? tr[zson] : tr[zson ^ 1];
      type[p] = (tr[zson] >= rd) ? (zson) : (zson ^ 1);
    }
  }
  for (int i = L; i; i--) {
    solve(i);
  }
  result[0] = 1;
  // cerr << "result: ";
  // for (int i = 0; i < n; i++) {
  //   cerr << result[i] << " ";
  // }
  // cerr << '\n';
  ll ans = 0;
  for (int i = 0; i < m; i++) {
    ans ^= (i + 1) * result[qry[i] - 1];
  }
  printf("%lld\n", ans);
}

int main() {
  freopen("arena.in", "r", stdin);
  freopen("arena.out", "w", stdout);
  scanf("%d%d", &n, &m);
  for (int i = 0; i < n; i++) {
    scanf("%d", _a + i);
  }
  for (int i = 0; i < m; i++) {
    scanf("%d", qry + i);
  }
  for (n2 = 1, L = 0; n2 < n; n2 <<= 1, L++);
  for (int i = 1; i <= L; i++) {
    int s = (1 << (L - i)), r = s;
    scanf("%s", buf);
    for (int j = 0; j < r; j++) {
      d[s + j] = buf[j] - '0';
//      rd[s + j] = i;
    }
  }
  scanf("%d", &T);
  while (T--) {
    solve();
  }
  return 0;
}
posted @ 2024-12-05 00:46  阿波罗2003  阅读(7)  评论(0编辑  收藏  举报