二分答案天数
calc(i, s, t):第 calc(i, mid, D) >= a[i] 时往右找更晚的)。种植顺序必须父先子后(以
1#include <bits/stdc++.h>
2using namespace std;
3const int maxn = 1e5 + 5;
4typedef long long ll;
5typedef __int128 i128;
6
7vector<int> g[maxn];
8int n, parent[maxn];
9ll a[maxn], b[maxn], c[maxn];
10int ddl[maxn];
11
12i128 calc(int i, int s, int t) {
13 if (c[i] >= 0 || b[i] + c[i] * t > 0) // 全程等差数列
14 return (i128)(b[i] + (ll)s * c[i] + b[i] + (ll)t * c[i]) * (t - s + 1) / 2;
15 int t0 = (b[i] - 1) / -c[i]; // 最后一次长 >=1 的天
16 if (s > t0) return t - s + 1;
17 return (i128)(b[i] + (ll)s * c[i] + b[i] + (ll)t0 * c[i]) * (t0 - s + 1) / 2 + (t - t0);
18}
19
20bool check(int D) {
21 for (int i = 1; i <= n; i++) {
22 int l = 0, r = D + 1; // 二分最晚种植日
23 while (l + 1 < r) {
24 int mid = (l + r) / 2;
25 if (calc(i, mid, D) >= a[i]) l = mid; else r = mid;
26 }
27 if (l == 0) return false;
28 ddl[i] = l;
29 }
30 priority_queue<pair<int,int>> q; // 大根堆按 ddl
31 vector<int> deg(n + 1);
32 for (int i = 2; i <= n; i++) deg[parent[i]]++;
33 for (int i = 1; i <= n; i++) if (deg[i] == 0) q.push({ddl[i], i});
34 int t = D;
35 while (!q.empty()) {
36 auto p = q.top(); q.pop();
37 if (t == 0) return false;
38 t = min(t, p.first) - 1;
39 int pa = parent[p.second];
40 if (--deg[pa] == 0) q.push({ddl[pa], pa});
41 }
42 return true;
43}
44
45void dfs(int u, int p) {
46 parent[u] = p;
47 for (int v : g[u]) if (v != p) dfs(v, u);
48}
49
50int main() {
51 ios::sync_with_stdio(false); cin.tie(0);
52 cin >> n;
53 for (int i = 1; i <= n; i++) cin >> a[i] >> b[i] >> c[i];
54 for (int i = 0; i < n - 1; i++) { int u, v; cin >> u >> v; g[u].push_back(v); g[v].push_back(u); }
55 dfs(1, 0);
56 int l = n - 1, r = 1e9;
57 while (l + 1 < r) { int x = (l + r) / 2; if (check(x)) r = x; else l = x; }
58 cout << r << '\n';
59}
calc 的 t0 是 ddl_i 二分:calc(i, mid, D) >= a[i] 时 l = mid(尽量晚种),l == 0 表示第 ddl 大的先安排,t 从 __int128 防溢出(| 测试点 | 特殊性质 | 拿法 | |
|---|---|---|---|
| 1 | 20 | A | 暴搜 |
| 2—4 | 20 | 无 | 暴搜 |
| 5—6 | 500 | A | 枚举天数 |
| 7—8 | A | 二分 + 贪心( |
|
| 9—10 | B | 链上贪心 | |
| 11—13 | C | 链上贪心 | |
| 14—16 | D | 菊花图贪心 | |
| 17—20 | 无 | 满分做法 |
特殊性质:A
这道题的难点是「最晚种植时间 + 树上父先子后的调度」两层结合,二分答案把它们串起来。
知识点:计数 DP
难度:省选/NOI−
设难题(
设
因此「拒绝或放弃」的总数就是解题的全局量:它不能超过
按耐心 cnt[i] 是耐心恰好为 sum[i] 是耐心
从 cnt[i] 人里,有
g,每层结束后前缀和 + 滚动到 f。最终 ans 累加所有「不再出现新的放弃、且难题都在最前面」的合法终态。
1#include <bits/stdc++.h>
2using namespace std;
3const int mod = 998244353;
4typedef long long ll;
5
6int n, m, cnt[505], sum[505];
7ll f[505][505], g[505][505], fact[505], choose[505][505];
8
9void range_add(int j1, int j2, int k, ll x) { g[j1][k] += x; g[j2][k] -= x; }
10
11int main() {
12 ios::sync_with_stdio(false); cin.tie(0);
13 string s; cin >> n >> m >> s;
14 int H = 0; for (char c : s) H += (c == '0');
15 vector<int> next_hard(n + 1); next_hard[n] = n;
16 for (int i = n - 1; i >= 0; i--) next_hard[i] = (s[i] == '0') ? i : next_hard[i + 1];
17 for (int i = 0; i < n; i++) { int c; cin >> c; cnt[c]++; }
18 for (int i = 0; i <= n; i++) sum[i + 1] = sum[i] + cnt[i];
19 if (H > n - m) { cout << 0 << '\n'; return 0; }
20 fact[0] = 1; for (int i = 1; i <= n; i++) fact[i] = fact[i - 1] * i % mod;
21 for (int i = 0; i <= n; i++) { choose[i][0] = 1; for (int j = 1; j <= i; j++) choose[i][j] = (choose[i - 1][j] + choose[i - 1][j - 1]) % mod; }
22 ll ans = 0;
23 f[0][0] = 1;
24 for (int i = 0; i <= n - m; i++) {
25 for (int j = 0; j <= n; j++)
26 for (int k = 0; k <= sum[i]; k++) {
27 if (f[j][k] == 0) continue;
28 for (int x = 0; x <= cnt[i]; x++) {
29 ll coeff = choose[cnt[i]][x] * fact[cnt[i] - x] % mod * choose[j - (sum[i] - k)][cnt[i] - x] % mod;
30 int q = next_hard[j];
31 if (k + x > 0) {
32 ll add = coeff * (k + x) % mod * f[j][k] % mod;
33 range_add(j + 1, q + 2, k + x - 1, add); // p ∈ (j, q]
34 }
35 range_add(q + 1, q + 2, k + x, coeff * f[j][k] % mod); // 全放到 q 之后
36 if (k + x == 0 && q == n) {
37 ans = (ans + coeff * f[j][k] % mod * fact[n - sum[i + 1]]) % mod;
38 }
39 }
40 }
41 for (int k = 0; k <= n; k++) for (int j = 1; j <= n; j++) g[j][k] = (g[j][k] + g[j - 1][k]) % mod;
42 memset(f, 0, sizeof f); swap(g, f);
43 }
44 cout << (ans % mod + mod) % mod << '\n';
45}
next_hard[j] 是位置 coeff 的三项:选 g,每层结束求前缀和并 swap(g, f) 滚动。sum[i]、fact、choose 预处理;| 测试点 | 特殊性质 | 拿法 | |
|---|---|---|---|
| 1—2 | 10 | 无 | 暴搜全排列 |
| 3—5 | 18 | 无 | 状压/暴搜 |
| 6—8 | A | DP | |
| 9—11 | 无 | DP | |
| 12—14 | 500 | 特化 | |
| 15 | 500 | 特化 | |
| 16—17 | 500 | A | |
| 18—21 | 500 | B | 状压 |
| 22—25 | 500 | 无 | 满分做法 |
特殊性质:A
这道题的难点在「耐心 ⟺ 第
知识点:树上 DP、倍增、(min,+) 矩阵
难度:省选/NOI−
树上有
最优路径的节点都在
给每个节点一个
a[0][0]=a[0][1]=v_i,a[1][0]=0,a[1][1]=INF。a[0][0]=a[0][1]=a[0][2]=v_i,a[1][0]=0、a[1][1]=w_i(邻居最小权)、a[1][2]=INF,a[2][1]=0、其余 INF。矩阵乘法是
把
prod_1:向上,矩阵从下往上乘(p1[u][j] = p1[anc][j-1] * p1[u][j-1])。prod_2:向下,矩阵从上往下乘(p2[u][j] = p2[u][j-1] * p2[anc][j-1])。答案
1#include <bits/stdc++.h>
2using namespace std;
3typedef long long ll;
4const ll INF = 4e18;
5const int maxn = 2e5 + 5;
6
7int n, Q, k, v[maxn];
8vector<int> g[maxn];
9int anc[maxn][18], depth[maxn];
10using mat = array<array<ll,3>,3>;
11
12mat get_I() { mat a; for (int i = 0; i < k; i++) for (int j = 0; j < k; j++) a[i][j] = (i == j ? 0 : INF); return a; }
13mat mul(const mat& a, const mat& b) { mat c;
14 for (int i = 0; i < k; i++) for (int j = 0; j < k; j++) { c[i][j] = INF;
15 for (int l = 0; l < k; l++) c[i][j] = min(c[i][j], a[i][l] + b[l][j]); }
16 return c; }
17mat p1[maxn][18], p2[maxn][18];
18
19mat prod_1(int u, int d) { mat a = get_I();
20 for (int i = 0; i < 18; i++) if (d >> i & 1) { a = mul(p1[u][i], a); u = anc[u][i]; } return a; }
21mat prod_2(int u, int d) { mat a = get_I();
22 for (int i = 0; i < 18; i++) if (d >> i & 1) { a = mul(a, p2[u][i]); u = anc[u][i]; } return a; }
23
24void dfs(int x, int p) { anc[x][0] = p; depth[x] = depth[p] + 1; for (int y : g[x]) if (y != p) dfs(y, x); }
25int lca(int x, int y) {
26 if (depth[x] < depth[y]) swap(x, y);
27 for (int i = 0; i < 18; i++) if ((depth[x]-depth[y]) >> i & 1) x = anc[x][i];
28 if (x == y) return y;
29 for (int i = 17; i >= 0; i--) if (anc[x][i] != anc[y][i]) x = anc[x][i], y = anc[y][i];
30 return anc[x][0];
31}
32
33mat make_mat2(int i) { mat a; a[0][0] = a[0][1] = v[i]; a[1][0] = 0; a[1][1] = INF; return a; }
34mat make_mat3(int i, vector<int>& w) { mat a;
35 a[0][0] = a[0][1] = a[0][2] = v[i];
36 a[1][0] = 0; a[1][1] = w[i]; a[1][2] = INF;
37 a[2][0] = INF; a[2][1] = 0; a[2][2] = INF; return a; }
38
39void solve_general(int last) {
40 for (int j = 1; j < 18; j++) for (int i = 1; i <= n; i++) {
41 int x = anc[i][j-1];
42 p1[i][j] = mul(p1[x][j-1], p1[i][j-1]);
43 p2[i][j] = mul(p2[i][j-1], p2[x][j-1]);
44 }
45 while (Q--) { int s, t; cin >> s >> t; int x = lca(s, t);
46 mat a = mul(prod_2(t, depth[t]-depth[x]), prod_1(s, depth[s]-depth[x]+1));
47 cout << a[0][last] << "\n"; }
48}
49
50int main() {
51 ios::sync_with_stdio(false); cin.tie(0);
52 cin >> n >> Q >> k;
53 for (int i = 1; i <= n; i++) cin >> v[i];
54 for (int i = 0; i < n-1; i++) { int a, b; cin >> a >> b; g[a].push_back(b); g[b].push_back(a); }
55 dfs(1, 0);
56 for (int j = 1; j < 18; j++) for (int i = 1; i <= n; i++) anc[i][j] = anc[anc[i][j-1]][j-1];
57 if (k == 1) {
58 vector<ll> sum(n + 1);
59 function<void(int,int)> f = [&](int x, int p) { sum[x] = sum[p] + v[x]; for (int y : g[x]) if (y != p) f(y, x); };
60 f(1, 0);
61 while (Q--) { int s, t; cin >> s >> t; int x = lca(s, t); cout << sum[s]+sum[t]-2*sum[x]+v[x] << "\n"; }
62 return 0;
63 }
64 if (k == 2) { for (int i = 1; i <= n; i++) p1[i][0] = p2[i][0] = make_mat2(i); solve_general(1); }
65 else { vector<int> w(n + 1, INT_MAX);
66 for (int i = 1; i <= n; i++) for (int j : g[i]) w[i] = min(w[i], v[j]);
67 for (int i = 1; i <= n; i++) p1[i][0] = p2[i][0] = make_mat3(i, w); solve_general(2); }
68 return 0;
69}
p1 从下往上、p2 从上往下,倍增时不能写反。a[0][k-1]:从「在路径上的 array,| 测试点 | 特殊性质 | 拿法 | ||
|---|---|---|---|---|
| 1 | 10 | 2 | 是 | 暴搜 |
| 2 | 10 | 3 | 是 | 暴搜 |
| 3 | 200 | 2 | 是 | 暴力建图 + 最短路 |
| 4—5 | 200 | 3 | 是 | 同上 |
| 6—7 | 2000 | 1 | 否 | 前缀和 |
| 8—9 | 2000 | 2 | 否 | 树上 DP |
| 10—11 | 2000 | 3 | 否 | 树上 DP |
| 12—13 | 1 | 否 | 前缀和 | |
| 14 | 2 | 是 | 倍增 | |
| 15—16 | 2 | 是 | 倍增 | |
| 17—19 | 2 | 否 | 倍增 | |
| 20 | 3 | 是 | 倍增 | |
| 21—22 | 3 | 是 | 倍增 | |
| 23—25 | 3 | 否 | 满分做法 |
特殊性质:保证
这道题把「路径上的最小花费」抽象成
知识点:平面图、对偶图最短路
难度:省选/NOI−
平面上
进行
平面图:能画在平面上、且任意两条边只在端点相交的图,网格图就是平面图。
最小割:给一部分顶点标成「源点」或「汇点」,把其余顶点也各归源、汇两侧;一条边若两端分属两侧,就是割边,所有割边的边权和最小就是最小割。
本题的染色正是一次「源/汇」划分:黑色格点归源侧、白色格点归汇侧,附加点的颜色固定、格点的颜色是变量。一条两端颜色不同的边,两端恰好分属两侧,正是割边。所以「异色边权和最小」= 这个平面图的最小割。
对偶图:把平面图的每个面(被边围成的区域)当成一个节点;原图每条边分隔两个面,在对偶图中连接这两个面,权值相同。
一条把源、汇分开的「割」对应一条横穿平面的曲线,这条曲线在对偶图里恰好是一条路径,割的边权和 = 这条路径的长度。所以平面图的
本题有多个黑白附加点(多终端),在平面图上仍是「黑白交界点两两配对的最短路之和」。网格里对偶图节点是小方格、对偶边是网格边;边界外用
按射线编号顺时针排附加点,相邻异色(
1#include <bits/stdc++.h>
2using namespace std;
3typedef long long ll;
4const ll INF = 1e16;
5const int MAXV = 260000, MAXE = 260000 * 8;
6
7int n, m, T, idx;
8int head[MAXV], tot = 1;
9struct Edge { int v, nxt; ll w; } e[MAXE];
10void add(int u, int v, ll w) { e[++tot] = {v, head[u], w}; head[u] = tot; e[++tot] = {u, head[v], w}; head[v] = tot; }
11int find(int i, int j) { return (i - 1) * m + j; }
12
13struct Add { ll x; int p, t; } a[60];
14int s[60], t[60], c[60], id[MAXV], cnts, cntt, cnt;
15ll dis[MAXV], g[60][60], dp[120][120];
16bool vis[MAXV];
17
18void dijkstra(int st) {
19 fill(dis, dis + MAXV, INF);
20 priority_queue<pair<ll,int>, vector<pair<ll,int>>, greater<>> pq;
21 dis[st] = 0; pq.push({0, st}); memset(vis, 0, sizeof(vis));
22 while (!pq.empty()) { int u = pq.top().second; pq.pop();
23 if (vis[u]) continue; vis[u] = true;
24 for (int i = head[u]; i; i = e[i].nxt) { int v = e[i].v;
25 if (dis[v] > dis[u] + e[i].w) { dis[v] = dis[u] + e[i].w; pq.push({dis[v], v}); } } }
26}
27int cir(int x) { return (x - 1) % cnt + 1; }
28
29void sol() {
30 int k; cin >> k;
31 for (int i = 1; i <= idx; i++) e[i << 1].w = e[i << 1 | 1].w = 0; // 重置环边权
32 for (int i = 1; i <= k; i++) { cin >> a[i].x >> a[i].p >> a[i].t; e[a[i].p << 1].w = e[a[i].p << 1 | 1].w = a[i].x; }
33 sort(a + 1, a + k + 1, [](Add& A, Add& B){ return A.p < B.p; });
34 cnts = cntt = cnt = 0;
35 if (a[1].t == 1 && a[k].t == 0) { s[++cnts] = a[1].p; c[++cnt] = a[1].p; }
36 if (a[1].t == 0 && a[k].t == 1) { t[++cntt] = a[1].p; c[++cnt] = a[1].p; }
37 for (int i = 2; i <= k; i++) {
38 if (a[i].t == 1 && a[i-1].t == 0) { s[++cnts] = a[i].p; c[++cnt] = a[i].p; }
39 if (a[i].t == 0 && a[i-1].t == 1) { t[++cntt] = a[i].p; c[++cnt] = a[i].p; }
40 }
41 if (cnts == 0) { cout << 0 << '\n'; return; }
42 sort(c + 1, c + cnt + 1);
43 for (int i = 1; i <= cnt; i++) id[c[i]] = i;
44 for (int i = 1; i <= cnt; i++) for (int j = 1; j <= cnt; j++) g[i][j] = INF;
45 for (int i = 1; i <= cnts; i++) { dijkstra(s[i]);
46 for (int j = 1; j <= cntt; j++) g[id[s[i]]][id[t[j]]] = g[id[t[j]]][id[s[i]]] = dis[t[j]]; }
47 for (int i = 1; i <= cnt * 2; i++) for (int j = 1; j <= cnt * 2; j++) dp[i][j] = INF;
48 for (int i = 1; i < cnt * 2; i++) dp[i][i+1] = g[cir(i)][cir(i+1)];
49 for (int len = 4; len <= cnt; len += 2)
50 for (int i = 1; i <= cnt * 2 - len + 1; i++) { int j = i + len - 1;
51 dp[i][j] = dp[i+1][j-1] + g[cir(i)][cir(j)];
52 for (int l = i + 1; l < j; l += 2) dp[i][j] = min(dp[i][j], dp[i][l] + dp[l+1][j]); }
53 ll ans = INF;
54 for (int i = 1; i <= cnt; i++) ans = min(ans, dp[i][i + cnt - 1]);
55 cout << ans << '\n';
56}
57
58int main() {
59 ios::sync_with_stdio(false); cin.tie(0);
60 cin >> n >> m >> T; idx = (n + m) * 2;
61 for (int i = 1; i < idx; i++) add(i, i + 1, 0);
62 add(idx, 1, 0);
63 for (int i = 1; i < n; i++) for (int j = 1; j <= m; j++) { ll x; cin >> x;
64 if (j == 1) add(idx + find(i, j), idx - i + 1, x);
65 else if (j == m) add(idx + find(i, j - 1), m + i + 1, x);
66 else add(idx + find(i, j - 1), idx + find(i, j), x); }
67 for (int i = 1; i <= n; i++) for (int j = 1; j < m; j++) { ll x; cin >> x;
68 if (i == 1) add(idx + find(i, j), j + 1, x);
69 else if (i == n) add(idx + find(i - 1, j), 2 * m + n - j + 1, x);
70 else add(idx + find(i - 1, j), idx + find(i, j), x); }
71 while (T--) sol();
72}
find(i,j)=(i-1)*m+j,边界环节点 | 测试点 | 拿法 | ||
|---|---|---|---|
| 1—2 | 5 | 50 | 暴搜染色 |
| 3—5 | 18 | 2 | 最大流/最短路 |
| 6—8 | 18 | 50 | 最大流 |
| 9—10 | 100 | 2 | 最短路 |
| 11—12 | 100 | 50 | 最大流 |
| 13—16 | 500 | 2 | 对偶图最短路 |
| 17—20 | 500 | 50 | 满分做法 |
这道题难在「最小割 = 对偶图最短路」的建模和边界环的处理,建好图后就是最短路 + 环形配对 DP。
知识点:博弈、归纳
难度:省选/NOI−
草原上有
最强的蛇吃了最弱的之后,若新体力仍不是最弱(安全),它一定吃;若新体力变成最弱(可能被下一条吃),则进入「犹豫」——它吃不吃要看后续。
犹豫链:从「吃了变最弱」开始,后续每轮最强蛇都面临同样的问题,直到某轮「吃了不变最弱」或「只剩
w1(pair<体力, 编号>)。强-弱 一定小于原来最强,所以 w2 也递减;每轮从两个队列头取最强、队尾取最弱。unsafe:吃了之后(还剩 parity 记录犹豫链奇偶;一旦回到「安全」,结算 parity 结束。1#include <bits/stdc++.h>
2using namespace std;
3typedef pair<int,int> pii;
4#define x first
5#define y second
6
7int n, a[1000005];
8namespace run {
9pii w1[1000005], w2[1000005];
10pii *l1, *r1, *l2, *r2;
11const pii inf(0x3f3f3f3f, 0x3f3f3f3f), ind(-0x3f3f3f3f, -0x3f3f3f3f);
12pii mx1() { return l1 <= r1 ? *l1 : ind; }
13pii mn1() { return l1 <= r1 ? *r1 : inf; }
14pii mx2() { return l2 <= r2 ? *l2 : ind; }
15pii mn2() { return l2 <= r2 ? *r2 : inf; }
16
17int n_eaten, parity;
18void mark_unsafe() { if (parity != -1) parity ^= 1; else parity = 0; }
19
20void solve() {
21 l2 = w2 + 1; r2 = w2;
22 l1 = w1 + 1; r1 = w1 + n;
23 for (int i = 1; i <= n; ++i) { w1[n + 1 - i].x = a[i]; w1[n + 1 - i].y = i; }
24 n_eaten = 0; parity = -1;
25 int s = n;
26 auto unsafe = [&](pii x) { return s > 1 && x < min(mn1(), mn2()); };
27 while (s > 1) {
28 pii mx, mn;
29 if (mx1() > mx2()) { mx = mx1(); ++l1; } else { mx = mx2(); ++l2; }
30 if (mn1() < mn2()) { mn = mn1(); --r1; } else { mn = mn2(); --r2; }
31 mx.x -= mn.x; --s;
32 if (unsafe(mx)) { mark_unsafe(); }
33 else { if (parity != -1) { n_eaten += parity; return; } ++n_eaten; }
34 *(++r2) = mx;
35 }
36}
37void main() { solve(); printf("%d\n", n - n_eaten); }
38}
39
40int main() {
41 int t; scanf("%d", &t);
42 for (int id = 1; id <= t; ++id) {
43 if (id == 1) { scanf("%d", &n); for (int i = 1; i <= n; ++i) scanf("%d", a + i); }
44 else { int k, x, y; scanf("%d", &k); for (; k; --k) { scanf("%d%d", &x, &y); a[x] = y; } }
45 run::main();
46 }
47 return 0;
48}
pair<体力,编号> 比较天然满足「体力大优先、相等编号大优先」,w1 用倒序填成递减。mx-mn 单调不增,w2 始终递减,所以两队列头/尾即全局最值,全程 unsafe 的判定用 x < min(mn1(), mn2()),注意只剩 a[x],数据保证 a 修改后仍不降序。| 测试点 | 拿法 | |
|---|---|---|
| 20% | 手推 | |
| 40% | 递归模拟 | |
| 55% | 暴力模拟 | |
| 70% | ||
| 100% | 双端队列 |
这道题的难点全在「危险就犹豫、奇偶定胜负」这条结论,推导清楚后模拟反而简单。
知识点:线段树、递归、阈值
难度:NOI/NOI+
把淘汰赛建成完全二叉树:叶节点是选手,内部节点
winner[i] 自底向上:d[i]=0 时左孩子是擂主,a[左胜者] >= r[i] 则左胜者晋级、否则右胜者晋级;d[i]=1 对称。递归到当前区间时,把区间外「可能成为冠军」的选手分成两类:champions 不论当前区间胜者能力值如何都可能夺冠;dependent_champions 只有当当前区间胜者的能力值不够大(赢不出去)时才可能夺冠。
f[x]:若当前区间胜者能力值为 d[i]=0/1 分两路,用 at_least(要赢出去的最低能力值)与 lim 做阈值判断。ans[叶子] = champions + dependent_champions + 自己编号。1#include <bits/stdc++.h>
2using namespace std;
3
4char d[1 << 18];
5int winner[1 << 18]; // winner[i]:对局 i 的胜者
6int r[1 << 18]; // r[i]:对局 i 所在轮数
7int a[1 << 17];
8int lp[1 << 18], rp[1 << 18]; // 区间左右端点
9long long sum[1 << 18];
10int N;
11long long ans[1 << 17];
12long long f[18]; // f[x]:若当前区间胜者能力值为 x,dependent champions 编号之和
13
14void dfs(int i, long long champions, long long dependent_champions, int at_least, int lim) {
15 if (i >= N) { ans[i - N] = champions + dependent_champions + (i - N + 1); return; }
16 if (d[i] == '0') { // 左孩子是擂主
17 if ((i & (i - 1)) == 0) dfs(i * 2, 0, 0, 0, 20);
18 else {
19 f[r[i] - 1] = dependent_champions + sum[2 * i + 1];
20 dfs(i * 2, champions, dependent_champions + sum[2 * i + 1], max(at_least, r[i]), max(r[i], lim));
21 }
22 if (a[winner[i * 2]] >= r[i]) { // 左孩子是胜者
23 long long s;
24 if (a[winner[i * 2]] >= at_least) s = winner[i * 2];
25 else if (a[winner[i * 2]] < lim) s = f[a[winner[i * 2]]];
26 else s = 0;
27 fill(ans + lp[2 * i + 1] - 1, ans + rp[2 * i + 1], champions + s);
28 } else { // 左孩子不是胜者
29 f[r[i] - 1] = f[r[i]];
30 dfs(2 * i + 1, champions, dependent_champions, at_least, lim);
31 }
32 } else { // 右孩子是擂主
33 if ((i & (i - 1)) == 0) dfs(i * 2, 0, 0, 0, 20);
34 else dfs(i * 2, champions + sum[i * 2 + 1] + dependent_champions, 0, at_least, 0);
35 if (a[winner[i * 2]] >= at_least) f[r[i] - 1] = winner[i * 2];
36 else if (a[winner[i * 2]] < lim) f[r[i] - 1] = f[max(a[winner[i * 2]], r[i])];
37 else f[r[i] - 1] = 0;
38 int new_dependent_champion = 0;
39 if (a[winner[i * 2]] >= at_least) new_dependent_champion = winner[i * 2];
40 dfs(i * 2 + 1, champions, dependent_champions + new_dependent_champion, max(at_least, r[i]), max(r[i], lim));
41 }
42}
43
44int main() {
45 int n, m; cin >> n >> m;
46 vector<int> A(n + 1), c(m + 1), round(m + 1);
47 for (int i = 1; i <= n; i++) cin >> A[i];
48 for (int i = 1; i <= m; i++) { cin >> c[i]; while (1 << round[i] < c[i]) round[i]++; }
49 int k = 0; while ((1 << k) < n) k++;
50 N = 1 << k;
51 for (int i = k - 1; i >= 0; i--) for (int j = 1 << i; j < 1 << (i + 1); j++) cin >> d[j];
52 for (int i = 0; i < N; i++) { r[N + i] = 0; sum[N + i] = lp[N + i] = rp[N + i] = winner[N + i] = i + 1; }
53 for (int i = N - 1; i >= 1; i--) {
54 r[i] = r[i * 2] + 1; lp[i] = lp[i * 2]; rp[i] = rp[i * 2 + 1]; sum[i] = sum[i * 2] + sum[i * 2 + 1];
55 }
56 int T; cin >> T;
57 while (T--) {
58 vector<int> x(4); for (int i = 0; i < 4; i++) cin >> x[i];
59 for (int i = 1; i <= n; i++) a[i] = A[i] xor x[i & 3];
60 for (int i = N - 1; i >= 1; i--) {
61 int L = winner[2 * i], R = winner[2 * i + 1];
62 if (d[i] == '0') winner[i] = a[L] >= r[i] ? L : R;
63 else winner[i] = a[R] >= r[i] ? R : L;
64 }
65 dfs(1, 0, 0, 0, 20);
66 long long sum = 0;
67 for (int i = 1; i <= m; i++) {
68 long long v = (1 << round[i]) == c[i] ? winner[N >> round[i]] : ans[c[i]];
69 sum ^= i * v;
70 }
71 cout << sum << '\n';
72 }
73}
winner[i] 依赖「擂主能力值 champions 与 dependent_champions 分别是不依赖/依赖当前区间胜者能力值的候选。at_least 是「赢出去的最低能力值」、lim 是阈值上界;f[x] 用能力值当下标存 dependent 和,避免枚举。fill 把同一段答案区间赋值。i * v 可能爆 int。| 测试点 | A | B | 拿法 | ||
|---|---|---|---|---|---|
| 1—3 | 1 | 8 | 否 | 否 | 暴搜 |
| 4—5 | 1 | 500 | 是 | 否 | 分治 |
| 6—8 | 1 | 500 | 否 | 是 | 分治 |
| 9—10 | 1 | 5000 | 否 | 否 | 分治 |
| 11—12 | 1 | 是 | 否 | 特化 | |
| 13—15 | 1 | 否 | 是 | 特化 | |
| 16—17 | 4 | 否 | 否 | 满分 | |
| 18—19 | 16 | 否 | 否 | 满分 | |
| 20—21 | 64 | 否 | 否 | 满分 | |
| 22—23 | 128 | 否 | 否 | 满分 | |
| 24—25 | 256 | 否 | 否 | 满分 |
特殊性质 A:所有
这道题是「能力值阈值」驱动的树上递推,分差全在能不能把「两类候选」的状态想清楚。
六道 T4 各归一类
二分答案 + 树上贪心:最晚种植时间 + 父先子后调度(种树)
计数 DP:耐心 ⟺ 第
树上 (min,+) 矩阵 + 倍增:偏离路径的层数当状态(数据传输)
平面图最小割 = 对偶图最短路:边界环 + 环形配对 DP(交通规划)
博弈归纳:安全就吃、危险就犹豫、奇偶定胜负(贪吃蛇)
阈值驱动的树上递推:两类候选 + 能力值当下标(擂台游戏)
一套查错习惯:二分边界、矩阵乘法方向、环形 DP 的环长、多组数据的清空