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)$)
对于前缀询问可以看作按顺序枚举每个选手,依次将每个选手从自由选手变为非自由选手。考虑改变时,从叶节点到根节点依次去更新树上每个点的赢家信息:
- 如果当前点赢家原先是自由选手,那么它可能变为非自由选手。
- 如果当前点赢家原先是非自由选手,也就是此时擂主已经是之前的非自由选手,结果依旧不会改变。
也就是意味着当一个点的赢家变为非自由选手时,它便确定了。当一个点确定时,一半的子树不能成为赢家。当它未确定的时候有两种情况:
- 擂主子树根确定但不能成为赢家,另一子树根未确定
- 擂主子树根为非自由选手。
注意到第一种情况中当前选手作为擂主子树,这种 case 不属于当前选手作为比赛中非擂主的情况。因此我们将非擂主的判定修改为:选手的所有祖先节点满足两个条件之一:节点非确定或节点确定且赢家为选手所在子树。
因此每个点相当会在某个时刻 $f_i$ 开始,ban 掉一半的子树。考虑预处理出每个点开始被 ban 掉的时间 $g_i$(在时刻 $i$,$i$ 号选手变为非自由选手),那么每个选手在时刻 $t$ 能成为赢家的条件是:
- $g_i > t$
- $i$ 为自由选手或 $a_i$ 满足作为擂主子树时胜利的条件(即大于某个最小能力值要求)。
对于作为擂主子树时胜利的条件,可以用一个简单的 dp 去预处理。详细讲一下 $f, g$ 怎么来转移。
- 对于 $f$:考虑点 $p$,记擂主子树和非擂主子树分别为 $u, v$,如果擂主子树未确定,那么一定未确定,当擂主子树确定时:
- 如果擂主子树不能成为赢家,那么 $f_p = \max\{f_{u}, f_v\}$
- 否则有,$f_p = f_u$
- 对于 $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; }