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

题目传送门

  传送门

Part 1 

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

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

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

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

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

  总时间复杂度 O(Tnmlogn),期望得分 40 ~ 48 分。

Part 2

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

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

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

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

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

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

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

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

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

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

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

  总时间复杂度 O(T(n+m)),期望得分 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;
}
posted @   阿波罗2003  阅读(19)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示