loj 4337 「CSP-S 2024」擂台游戏 - 动态规划
题目传送门
Part 1
先考虑 的情形,先考虑怎么判定每个选手有无可能成为赢家。
比赛过程可以看成一棵满二叉树。每个选手是树的一个叶节点。
当前选手 依次进行的比赛可以分为两种: 作为擂主和兄弟节点 的子树中的赢家作为擂主。
- 判定前者只需要检查 作为擂主时的最大轮次和 的大小(如果 为自由选手,那么将 视为 即可)
- 判定后者考虑将子树内所有的自由节点设为当前轮次 - 1(即在子树内作为擂主时必定晋级,但是在当前点时必定不能晋级),然后判断子树根的值和当前轮次的大小。
对于后面一部分判断,其实在被考虑子树中,自由节点座位擂主时必定晋级。因此我们将所有自由选手视为 ,先预处理出每个点最终的赢家的权值(自由选手或者非自由选手的确定值)。对于每个查询,依次考虑每个选手,暴力跳祖先去检查是否能获胜。
总时间复杂度 ,期望得分 40 ~ 48 分。
Part 2
我们希望不要每个询问都去暴力枚举每个点计算答案。考虑对于每种高度的树单独处理出所有询问答案。(注意 )
对于前缀询问可以看作按顺序枚举每个选手,依次将每个选手从自由选手变为非自由选手。考虑改变时,从叶节点到根节点依次去更新树上每个点的赢家信息:
- 如果当前点赢家原先是自由选手,那么它可能变为非自由选手。
- 如果当前点赢家原先是非自由选手,也就是此时擂主已经是之前的非自由选手,结果依旧不会改变。
也就是意味着当一个点的赢家变为非自由选手时,它便确定了。当一个点确定时,一半的子树不能成为赢家。当它未确定的时候有两种情况:
- 擂主子树根确定但不能成为赢家,另一子树根未确定
- 擂主子树根为非自由选手。
注意到第一种情况中当前选手作为擂主子树,这种 case 不属于当前选手作为比赛中非擂主的情况。因此我们将非擂主的判定修改为:选手的所有祖先节点满足两个条件之一:节点非确定或节点确定且赢家为选手所在子树。
因此每个点相当会在某个时刻 开始,ban 掉一半的子树。考虑预处理出每个点开始被 ban 掉的时间 (在时刻 , 号选手变为非自由选手),那么每个选手在时刻 能成为赢家的条件是:
- 为自由选手或 满足作为擂主子树时胜利的条件(即大于某个最小能力值要求)。
对于作为擂主子树时胜利的条件,可以用一个简单的 dp 去预处理。详细讲一下 怎么来转移。
- 对于 :考虑点 ,记擂主子树和非擂主子树分别为 ,如果擂主子树未确定,那么一定未确定,当擂主子树确定时:
- 如果擂主子树不能成为赢家,那么
- 否则有,
- 对于 :先处理出 。自顶向下考虑,设 的父节点为 。
- 如果 确定时,允许 成为赢家,那么
- 否则有
对于每个选手能成为赢家的时间一个区间,差分后前缀和即可。
总时间复杂度 ,期望得分 100 分。
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 | #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; } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现