1 条题解
-
0
自动搬运
来自洛谷,原作者为

Engulf
あまねく奇跡の始発点搬运于
2025-08-24 23:13:10,当前版本为作者最后更新于2025-07-31 22:22:34,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
:::warning[写在前面]{open}
本题解十分详细。按照正常思考顺序编写,可以跳转到自己卡壳的地方开始阅读。
只有绿色折叠框的代码 6 能够通过本题。 :::
subtask 1
暴力,枚举每个位置激活与否,若激活,激活了后面的哪个。
。
:::info[代码 1]
#include <bits/stdc++.h> using namespace std; using ll = long long; using pii = pair<int, int>; #ifdef ONLINE_JUDGE #define debug(...) 0 #else #define debug(...) fprintf(stderr, __VA_ARGS__), fflush(stderr) #endif const int N = 2e4 + 5; int n, q, k; int h[N], a[N], b[N]; int backup[N]; int dfs(int u, int l, int r, int x) { if (u > r) { return *max_element(h + l, h + 1 + r) <= x; } if (dfs(u + 1, l, r, x)) return true; for (int j = u + 1; j <= min(u + k, r); j++) { h[u] += a[u]; h[j] -= b[u]; if (dfs(u + 1, l, r, x)) return true; h[u] -= a[u]; h[j] += b[u]; } return false; } void subtask1() { while (q--) { int l, r, x; cin >> l >> r >> x; for (int i = l; i <= r; i++) h[i] = backup[i]; cout << (dfs(l, l, r, x) ? "Yes\n" : "No\n"); } } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int c, tt; cin >> c >> tt; while (tt--) { cin >> n >> q >> k; for (int i = 1; i <= n; i++) cin >> h[i], backup[i] = h[i]; for (int i = 1; i <= n; i++) cin >> a[i]; for (int i = 1; i <= n; i++) cin >> b[i]; subtask1(); } return 0; }:::
代码 1 期望获得 分,实际获得 分。
subtask 2 & 3
,考虑状压 dp。
设 表示:处理至点 ,往前 个点(包括点 )的状态为 ,此时前 个数的最大值 的最小值。
其中 是一个二进制数, 的第 位为 表示点 已激活,但还没有往后贡献(对应题目中就是,激活了点 ,但还没往后选择点 )。
转移的时候,枚举 的子集 ,让 中的点贡献给 ,即让 减去 中点的 。
而点 有激活与不激活两种选项,分别写出转移:
- 不激活点 :$$f_{i + 1, (S \oplus T) \ll 1} = \min\left(f_{i + 1, (S \oplus T) \ll 1}, \max\left(f_{i, S}, h_{i + 1} - \sum\limits_{x \in T}b_x\right)\right) $$
- 激活点 :$$f_{i + 1, (S \oplus T) \ll 1 | 1} = \min\left(f_{i + 1, (S \oplus T) \ll 1 | 1}, \max\left(f_{i, S}, h_{i + 1} + a_{i + 1} - \sum\limits_{x \in T} b_x\right)\right) $$
其中 表示去掉了子集 (贡献给了 ),或上 表示激活点 ,因为只处理到 ,肯定是无法往后贡献的。
边界条件:。其他状态初始设置为 。
若 (所有激活了的点都贡献了出去),答案为
Yes,否则为No。。
:::info[代码 2]
#include <bits/stdc++.h> using namespace std; using ll = long long; using pii = pair<int, int>; #ifdef ONLINE_JUDGE #define debug(...) 0 #else #define debug(...) fprintf(stderr, __VA_ARGS__), fflush(stderr) #endif const int N = 2e4 + 5; const int inf = 0x3f3f3f3f; int n, q, k; int h[N], a[N], b[N]; int f[N][32]; void chkmin(int &x, int y) {x = min(x, y);} int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int tc, tt; cin >> tc >> tt; while (tt--) { cin >> n >> q >> k; for (int i = 1; i <= n; i++) cin >> h[i]; for (int i = 1; i <= n; i++) cin >> a[i]; for (int i = 1; i <= n; i++) cin >> b[i]; while (q--) { int l, r, x; cin >> l >> r >> x; for (int i = l; i <= r; i++) for (int j = 0; j < (1 << k); j++) f[i][j] = inf; f[l][0] = h[l], f[l][1] = h[l] + a[l]; for (int i = l; i < r; i++) { for (int j = 0; j < (1 << min(k, i - l + 1)); j++) { chkmin(f[i + 1][j << 1 & 31], max(f[i][j], h[i + 1])); chkmin(f[i + 1][(j << 1 | 1) & 31], max(f[i][j], h[i + 1] + a[i + 1])); for (int s = j; s; s = (s - 1) & j) { int d = 0; for (int t = 0; t < min(k, i - l + 1); t++) if (s >> t & 1) if (i - t >= l) d += b[i - t]; chkmin(f[i + 1][(j ^ s) << 1 & 31], max(f[i][j], h[i + 1] - d)); chkmin(f[i + 1][((j ^ s) << 1 | 1) & 31], max(f[i][j], h[i + 1] + a[i + 1] - d)); } } } cout << (f[r][0] <= x ? "Yes\n" : "No\n"); } } return 0; }:::
代码 2 期望获得 分,实际获得 分(这份代码没有特殊处理特殊性质 A,A 的话只要跑一遍全局的 dp 就行)。
subtask 4 ~ 7
如何加速区间询问?
观察转移方程:
$$f_{i + 1, (S \oplus T) \ll 1} = \min\left(f_{i + 1, (S \oplus T) \ll 1}, \max\left(f_{i, S}, h_{i + 1} - \sum\limits_{x \in T}b_x\right)\right) $$$$f_{i + 1, (S \oplus T) \ll 1 | 1} = \min\left(f_{i + 1, (S \oplus T) \ll 1 | 1}, \max\left(f_{i, S}, h_{i + 1} + a_{i + 1} - \sum\limits_{x \in T} b_x\right)\right) $$可以发现它们的形式都类似这样:
$$f_{i + 1, U} = \min\left(f_{i + 1, U}, \max\left(f_{i, S}, v\right)\right) $$由于 运算对 运算有分配律(反过来也成立哦),上式可以矩阵乘法加速,定义转移矩阵 ,那么 。
进一步发现,每个 对应的转移矩阵 是一样的,问题转化成了求静态的区间矩阵乘积,使用线段树维护。
注意询问的时候,查到线段树上对应的某个区间的矩阵乘积,直接用答案向量 去乘它,不要把一堆这些矩阵乘在一次,不然时间复杂度会多乘上一个 。
。
:::info[代码 3]
#include <bits/stdc++.h> using namespace std; using ll = long long; using pii = pair<int, int>; #ifdef ONLINE_JUDGE #define debug(...) 0 #else #define debug(...) fprintf(stderr, __VA_ARGS__), fflush(stderr) #endif const int N = 2e4 + 5; const int inf = 0x3f3f3f3f; int n, q, k; int h[N], a[N], b[N]; // int f[N][32]; void chkmin(int &x, int y) {x = min(x, y);} using Matrix = vector<vector<int>>; Matrix I() {Matrix I(1 << k, vector<int>(1 << k, inf)); for (int i = 0; i < I.size(); i++) I[i][i] = -inf; return I;} Matrix operator*(const Matrix &a, const Matrix &b) { Matrix c(1 << k, vector<int>(1 << k, inf)); for (int i = 0; i < a.size(); i++) for (int j = 0; j < b[0].size(); j++) for (int k = 0; k < b.size(); k++) c[i][j] = min(c[i][j], max(a[i][k], b[k][j])); return c; } Matrix tr[N << 2]; void build(int p, int l, int r) { if (l == r) { tr[p].assign(1 << k, vector<int>(1 << k, inf)); for (int j = 0; j < (1 << k); j++) { chkmin(tr[p][j][j << 1 & (1 << k) - 1], h[l + 1]); chkmin(tr[p][j][(j << 1 | 1) & (1 << k) - 1], h[l + 1] + a[l + 1]); for (int s = j; s; s = (s - 1) & j) { int d = 0; bool valid = true; for (int t = 0; t < k; t++) if (s >> t & 1) { if (l - t < 1) { valid = false; break; } d += b[l - t]; } if (!valid) continue; chkmin(tr[p][j][(j ^ s) << 1 & (1 << k) - 1], h[l + 1] - d); chkmin(tr[p][j][((j ^ s) << 1 | 1) & (1 << k) - 1], h[l + 1] + a[l + 1] - d); } } return; } int mid = l + r >> 1; build(p << 1, l, mid); build(p << 1 | 1, mid + 1, r); tr[p] = tr[p << 1] * tr[p << 1 | 1]; } void query(int p, int l, int r, int L, int R, Matrix &f) { if (L > R || r < L || l > R) return; if (L <= l && r <= R) {f = f * tr[p]; return;} int mid = l + r >> 1; query(p << 1, l, mid, L, R, f); query(p << 1 | 1, mid + 1, r, L, R, f); } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int tc, tt; cin >> tc >> tt; while (tt--) { cin >> n >> q >> k; for (int i = 1; i <= n; i++) cin >> h[i]; for (int i = 1; i <= n; i++) cin >> a[i]; for (int i = 1; i <= n; i++) cin >> b[i]; build(1, 1, n - 1); while (q--) { int l, r, x; cin >> l >> r >> x; Matrix f(1, vector<int>(1 << k, inf)); f[0][0] = h[l], f[0][1] = h[l] + a[l]; // auto A = query(1, 1, n, l, r - 1); query(1, 1, n - 1, l, r - 1, f); cout << (f[0][0] <= x ? "Yes\n" : "No\n"); } } return 0; }:::
代码 3 期望得分 分,实际得分 分(常数太大)。
subtask 8
观察特殊性质 B:所有 均相等。
实际上我们只关心每个数相对 的大小关系,不妨令 的数为 , 的数为 ,把 的矩阵改成 个
bitset,每个bitset位。其他同上。用
bitset加速矩阵乘法,具体地,原本的矩阵乘法形如改成 后可以用位运算
$$c_{i, j} = \text{and}_k(a_{i, k} \ \text{or} \ b_{k, j}) $$这一维可以省略了,仅需枚举 和 。
-
若 ,后面 ,也就是 一定为 ,前面对 取 显然不会影响 。
-
若 ,它不会影响 ,可以直接拿掉,
既然用了
bitset,就直接 即可,复杂度除以 。
。
:::info[代码 4]
#include <bits/stdc++.h> using namespace std; using ll = long long; using pii = pair<int, int>; #ifdef ONLINE_JUDGE #define debug(...) 0 #else #define debug(...) fprintf(stderr, __VA_ARGS__), fflush(stderr) #endif const int N = 2e4 + 5; const ll inf = (1ll << 32) - 1; int n, q, k, x; int h[N], a[N], b[N]; // int f[N][32]; using Matrix = vector<bitset<32>>; ostream& operator<<(ostream &os, const Matrix &b) { for (int i = 0; i < b.size(); i++) { os << b[i]; if (i != b.size() - 1) os << "\n"; } return os; } Matrix operator*(const Matrix &a, const Matrix &b) { Matrix c(32, bitset<32>(inf)); for (int i = 0; i < a.size(); i++) for (int k = 0; k < b.size(); k++) if (!a[i][k]) c[i] &= b[k]; // for (int i = 0; i < a.size(); i++) // for (int j = 0; j < b[0].size(); j++) // for (int k = 0; k < b.size(); k++) // c[i][j] = c[i][j] & (a[i][k] | b[k][j]); return c; } Matrix tr[N << 2]; void build(int p, int l, int r) { if (l == r) { tr[p].assign(32, bitset<32>(inf)); for (int j = 0; j < (1 << k); j++) { tr[p][j][j << 1 & 31] = (h[l + 1] > x); tr[p][j][(j << 1 | 1) & 31] = (h[l + 1] + a[l + 1] > x); for (int s = j; s; s = (s - 1) & j) { int d = 0; bool valid = true; for (int t = 0; t < k; t++) if (s >> t & 1) { if (l - t < 1) { valid = false; break; } d += b[l - t]; } if (!valid) continue; tr[p][j][(j ^ s) << 1 & 31] = tr[p][j][(j ^ s) << 1 & 31] & (h[l + 1] - d > x); tr[p][j][((j ^ s) << 1 | 1) & 31] = tr[p][j][((j ^ s) << 1 | 1) & 31] & (h[l + 1] + a[l + 1] - d > x); } } return; } int mid = l + r >> 1; build(p << 1, l, mid); build(p << 1 | 1, mid + 1, r); tr[p] = tr[p << 1] * tr[p << 1 | 1]; } void query(int p, int l, int r, int L, int R, Matrix &f) { if (L > R || r < L || l > R) return; if (L <= l && r <= R) {f = f * tr[p]; return;} int mid = l + r >> 1; query(p << 1, l, mid, L, R, f); query(p << 1 | 1, mid + 1, r, L, R, f); } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int tc, tt; cin >> tc >> tt; while (tt--) { cin >> n >> q >> k; for (int i = 1; i <= n; i++) cin >> h[i]; for (int i = 1; i <= n; i++) cin >> a[i]; for (int i = 1; i <= n; i++) cin >> b[i]; int l, r; cin >> l >> r >> x; build(1, 1, n - 1); while (q--) { Matrix f(32, bitset<32>(inf)); f[0][0] = (h[l] > x), f[0][1] = (h[l] + a[l] > x); query(1, 1, n - 1, l, r - 1, f); cout << (!f[0][0] ? "Yes\n" : "No\n"); if (q) cin >> l >> r >> x; } } return 0; }:::
代码 4 期望得分 分,实际得分 分(常数太大)。
subtask 9 & 10
考虑推广特殊性质 B。将询问离线,将所有询问和 这样的修改,按照 (对于修改,则是 )从小到大排序,优先修改后查询。
初始时从小到大遍历询问和修改,如果是询问,正常查询即可。
如果是修改,发现修改仅仅是将线段树某个叶子节点的矩阵的第 行 列置 ,如果 pushup 的时候直接左儿子矩阵乘右儿子矩阵,复杂度爆炸。
我们只考虑哪些位置要重新更新。叶子节点直接更改。
接下来,令左儿子的矩阵是 ,右儿子的矩阵是 ,该节点的矩阵是 。
$$c_{i, j} = \text{and}_k(a_{i, k} \ \text{or} \ b_{k, j}) $$依次往上看,先看叶子节点的父亲 ,发现儿子在左子树还是右子树的情况不同,分类讨论:
- 若修改来自 的左儿子,不妨假设修改是把 置 。
- 若 , 一定为 ,不会影响前面的 。
- 也就是只有 的位置可能会影响 ,只要找 所有 的位置,把 置 即可。
- 若修改来自 的右儿子,不妨假设修改是把 置 。
- 若 , 一定为 ,不会影响前面的 。
- 也就是只有 的位置会影响 ,只要找所有第 位是 的 ,把 置 即可。
对于左儿子,我们知道 libstdc++ 为 bitset 提供了方便的
_Find_first()和_Find_next(pos)成员函数,但这是找 的,把 取反找 就等价于找 了。对于右儿子,发现跨了
bitset,有点棘手?记录转置矩阵 ,修改是同步的,只有在这一步,要找所有第 位是 的 ,只需要找 所有的 即可,同上取反找 即可。注意,一个 被置 后就不会再变回 ,要保证每次找的 一共只被找 次,可以使用一些位运算技巧。
:::info[技巧] 因为是实现细节,所以放在了折叠框里。
左右儿子情况类似,不妨只讨论左儿子。
我们要找到所有位置 ,使得取反后的 ,且这个位置没被我们找过,这意味着 。我们的处理把 取反得到 ,现在要找 且 的位置,直接令 即可。
右儿子就是把 换成了 。 :::
发现现在 又有若干个置 的修改,用 vector 存下这些修改,在父亲处继续修改即可。
每个点我们保证了只有一次 变成 ,一共有 个这样的操作。复杂度可接受。
。
:::info[代码 5]
#include <bits/stdc++.h> using namespace std; using ll = long long; using pii = pair<int, int>; #ifdef ONLINE_JUDGE #define debug(...) 0 #else #define debug(...) fprintf(stderr, __VA_ARGS__), fflush(stderr) #endif const int N = 2e4 + 5, M = 1e5 + 5; const unsigned inf = ~0; int n, m, k; int h[N], a[N], b[N]; using Matrix = vector<bitset<32>>; int ans[M]; struct Query { int l, r, x, id; int typ; Query(int l, int r, int x, int id, int typ) : l(l), r(r), x(x), id(id), typ(typ) {} bool operator<(const Query &b) const {return x != b.x ? x < b.x : typ < b.typ;} }; vector<Query> q; ostream& operator<<(ostream &os, const Matrix &b) { for (int i = 0; i < b.size(); i++) { os << b[i]; if (i != b.size() - 1) os << "\n"; } return os; } bitset<32> operator*(const bitset<32> &a, const Matrix &b) { bitset<32> c(inf); for (int k = 0; k < b.size(); k++) if (!a[k]) c &= b[k]; return c; } // Matrix operator*(const Matrix &a, const Matrix &b) { // Matrix c(32, bitset<32>(inf)); // for (int i = 0; i < a.size(); i++) // for (int k = 0; k < b.size(); k++) // if (!a[i][k]) // c[i] &= b[k]; // return c; // } Matrix tr[N << 2], tr2[N << 2]; vector<pii> add[N << 2]; void build(int p, int l, int r) { tr[p].assign(32, bitset<32>(inf)); tr2[p].assign(32, bitset<32>(inf)); if (l == r) return; int mid = l + r >> 1; build(p << 1, l, mid); build(p << 1 | 1, mid + 1, r); } void modify(int p, int l, int r, int x, int s, int t) { if (l == r) { tr[p][s][t] = 0; tr2[p][t][s] = 0; add[p].emplace_back(s, t); return; } int mid = l + r >> 1; if (x <= mid) { modify(p << 1, l, mid, x, s, t); for (auto [s, t]: add[p << 1]) { auto rev = ~tr[p << 1 | 1][t]; rev &= tr[p][s]; for (int k = rev._Find_first(); k < 32; k = rev._Find_next(k)) { tr[p][s][k] = tr2[p][k][s] = 0; add[p].emplace_back(s, k); } } add[p << 1].clear(); add[p << 1].shrink_to_fit(); } else { modify(p << 1 | 1, mid + 1, r, x, s, t); for (auto [s, t]: add[p << 1 | 1]) { auto rev = ~tr2[p << 1][s]; rev &= tr2[p][t]; for (int i = rev._Find_first(); i < 32; i = rev._Find_next(i)) { tr[p][i][t] = tr2[p][t][i] = 0; add[p].emplace_back(i, t); } } add[p << 1 | 1].clear(); add[p << 1 | 1].shrink_to_fit(); } // if (corp != tr[p]) { // debug("Let's check out the mergement of matrix %d\n", p); // debug("the modify comes from %s, and s = %d, t = %d\n", lef ? "left" : "right", s, t); // cerr << "left matrix: \n" << tr[p << 1] << "\n"; // cerr << "right matrix: \n" << tr[p << 1 | 1] << "\n"; // cerr << "correct matrix " << p << ":\n" << corp << "\n"; // cerr << "present matrix " << p << ": \n" << tr[p] << "\n\n"; // } } void query(int p, int l, int r, int L, int R, bitset<32> &f) { if (L > R || r < L || l > R) return; if (L <= l && r <= R) {f = f * tr[p]; return;} int mid = l + r >> 1; query(p << 1, l, mid, L, R, f); query(p << 1 | 1, mid + 1, r, L, R, f); } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int tc, tt; cin >> tc >> tt; while (tt--) { cin >> n >> m >> k; for (int i = 1; i <= n; i++) cin >> h[i]; for (int i = 1; i <= n; i++) cin >> a[i]; for (int i = 1; i <= n; i++) cin >> b[i]; q.clear(); for (int i = 1, l, r, x; i <= m; i++) { cin >> l >> r >> x; q.emplace_back(l, r, x, i, 1); } for (int i = 1; i < n; i++) for (int j = 0; j < (1 << k); j++) { q.emplace_back(j, j << 1 & 31, h[i + 1], i, 0); q.emplace_back(j, (j << 1 | 1) & 31, h[i + 1] + a[i + 1], i, 0); for (int s = j; s; s = (s - 1) & j) { int d = 0; bool valid = true; for (int t = 0; t < k; t++) if (s >> t & 1) { if (i - t < 1) { valid = false; break; } d += b[i - t]; } if (!valid) continue; q.emplace_back(j, (j ^ s) << 1 & 31, h[i + 1] - d, i, 0); q.emplace_back(j, ((j ^ s) << 1 | 1) & 31, h[i + 1] + a[i + 1] - d, i, 0); } } build(1, 1, n - 1); sort(q.begin(), q.end()); for (auto [l, r, x, id, typ]: q) { if (typ) { // Matrix f(32, bitset<32>(inf)); // f[0][0] = h[l] > x, f[0][1] = h[l] + a[l] > x; bitset<32> f(inf); f[0] = h[l] > x, f[1] = h[l] + a[l] > x; query(1, 1, n - 1, l, r - 1, f); ans[id] = !f[0]; } else { modify(1, 1, n - 1, id, l, r); } } for (int i = 1; i <= m; i++) cout << (ans[i] ? "Yes\n" : "No\n"); } return 0; }:::
代码 5 期望得分 ,实际得分 。
卡常
:::warning{open} 本题卡常严重。 :::
这部分更多算是我的一些碎碎念,感觉偏离了题解本身教授题目做法的宗旨,所以我放在了折叠框里。
如果你觉得阅读完上面的题解后,认为自己能够写出复杂度正确且常数较小的优秀做法,你可以不用打开折叠框。但如果你认为自己可能会被卡常,可以阅读一下,了解一些注意事项。
:::info[卡常]
从代码 3 开始就埋下伏笔了。
代码 5 的复杂度正确,但因为常数巨大只能获得 分。仔细分析不难发现,代码 5 的常数巨大主要由两方面导致:
实现本身的不够优。
排序
排序是很难被注意到的一点。
我们将所有的询问和修改放在了一起,可以发现,极端情况下询问和修改的总数是 ,直接排序乘上 多的 ,不优。
降低排序的复杂度就考虑计数排序, 的值域上限是 ,下限是 减去 个最大的 就是 。值域大小 ,可以接受。
计数排序的时间复杂度是 , 是询问和修改的总数。
构造转移矩阵
如果你看了我的代码 5 的实现,会发现那是我直接在 dp 的基础上改的。
q.emplace_back(j, (j ^ s) << 1 & 31, h[i + 1] - d, i, 0); q.emplace_back(j, ((j ^ s) << 1 | 1) & 31, h[i + 1] + a[i + 1] - d, i, 0);我直接把所有的修改拿出来排序。但修改有很多是打在了同一个位置的,因为一个 对应了最多 个状态,这里跟 dp 的时候不一样,dp 的时候没有什么 的修改,只有 的取 。这是非常严重的失误,显然只有最小的 有用。 改成 后不会再修改。这可以显著减少修改。
上面这两个是导致常数巨大的主要原因。但还是在一些数据上跑得慢,剩下的就是常规的卡常技巧。
常规卡常
我做了包括但不限于下面的常规卡常:
- 快读快写;
- 少用 vector;
- 修改的时候,记录一下序列的第 个对应线段树哪个节点,直接从底往上修改,就不用再递归去找叶子,没有修改直接退出;
- 全局
bitset。 :::
:::success[代码 6]
#include <bits/stdc++.h> using namespace std; using ll = long long; using pii = pair<int, int>; #ifdef ONLINE_JUDGE #define debug(...) 0 #else #define debug(...) fprintf(stderr, __VA_ARGS__), fflush(stderr) #endif namespace fastio{ // from MiniLong #ifdef ONLINE_JUDGE char ibuf[1 << 20],*p1 = ibuf, *p2 = ibuf; #define get() p1 == p2 && (p2 = (p1 = ibuf) + fread(ibuf, 1, 1 << 20, stdin), p1 == p2) ? EOF : *p1++ #else #define get() getchar() #endif template<typename T> inline void read(T &t){ T x = 0, f = 1; char c = getchar(); while(!isdigit(c)){ if(c == '-') f = -f; c = getchar(); } while(isdigit(c)) x = x * 10 + c - '0', c = getchar(); t = x * f; } template<typename T, typename ... Args> inline void read(T &t, Args&... args){ read(t); read(args...); } template<typename T> void write(T t){ if(t < 0) putchar('-'), t = -t; if(t >= 10) write(t / 10); putchar(t % 10 + '0'); } template<typename T, typename ... Args> void write(T t, Args... args){ write(t), putchar(' '), write(args...); } template<typename T> void writeln(T t){ write(t); puts(""); } template<typename T> void writes(T t){ write(t), putchar(' '); } #undef get }; using namespace fastio; const int N = 2e4 + 5, M = 1e5 + 5, Q = 5e6; const unsigned inf = ~0; int n, m, k; int h[N], a[N], b[N]; int ans[M]; struct Query { int l, r, x, id; int typ; Query() {} Query(int l, int r, int x, int id, int typ) : l(l), r(r), x(x), id(id), typ(typ) {} bool operator<(const Query &b) const {return x != b.x ? x < b.x : typ < b.typ;} } q[M + N * 486], qq[M + N * 486]; bitset<32> tr[N << 2][32], tr2[N << 2][32]; vector<pii> add[N << 2]; int reff[N]; void build(int p, int l, int r) { for (int i = 0; i < 32; i++) { tr[p][i].set(); tr2[p][i].set(); } if (l == r) return reff[l] = p, void(); int mid = l + r >> 1; build(p << 1, l, mid); build(p << 1 | 1, mid + 1, r); } void modify(int x, int s, int t) { int p = reff[x]; if (!tr[p][s][t]) return; tr[p][s][t] = tr2[p][t][s] = 0; add[p].emplace_back(s, t); p >>= 1; while (p) { if (add[p << 1].empty() && add[p << 1 | 1].empty()) break; for (auto [s, t]: add[p << 1]) { auto rev = ~tr[p << 1 | 1][t] & tr[p][s]; for (int i = rev._Find_first(); i < (1 << k); i = rev._Find_next(i)) { tr[p][s][i] = tr2[p][i][s] = 0; add[p].emplace_back(s, i); } } add[p << 1].clear(); for (auto [s, t]: add[p << 1 | 1]) { auto rev = ~tr2[p << 1][s] & tr2[p][t]; for (int i = rev._Find_first(); i < (1 << k); i = rev._Find_next(i)) { tr[p][i][t] = tr2[p][t][i] = 0; add[p].emplace_back(i, t); } } add[p << 1 | 1].clear(); p >>= 1; } } bitset<32> f, c; void query(int p, int l, int r, int L, int R) { if (L > R || r < L || l > R) return; if (L <= l && r <= R) { c.set(); for (int i = 0; i < (1 << k); i++) if (!f[i]) c &= tr[p][i]; f = c; return; } int mid = l + r >> 1; query(p << 1, l, mid, L, R); query(p << 1 | 1, mid + 1, r, L, R); } int cnt[7000005]; int g[32]; int main() { int tc, tt; read(tc, tt); int maxx = 0; while (tt--) { read(n, m, k); for (int i = 1; i <= n; i++) read(h[i]); for (int i = 1; i <= n; i++) read(a[i]); for (int i = 1; i <= n; i++) read(b[i]); for (int i = 1; i <= maxx; i++) cnt[i] = 0; int lenq = 0; for (int i = 1; i < n; i++) for (int j = 0; j < (1 << k); j++) { for (int t = 0; t < 32; t++) g[t] = 2e9; for (int s = j; ; s = (s - 1) & j) { int d = 0; bool valid = true; for (int t = 0; t < k; t++) if (s >> t & 1) { if (i - t < 1) { valid = false; break; } d += b[i - t]; } if (!valid) continue; g[(j ^ s) << 1 & 31] = min(g[(j ^ s) << 1 & 31], h[i + 1] - d); g[((j ^ s) << 1 | 1) & 31] = min(g[((j ^ s) << 1 | 1) & 31], h[i + 1] + a[i + 1] - d); // q[++lenq] = {j, (j ^ s) << 1 & 31, h[i + 1] - d, i, 0}, maxx = max(maxx, h[i + 1] - d + Q); // debug("[%d][%d] = %d\n", j, (j ^ s) << 1 & 31, h[i + 1] - d); // q[++lenq] = {j, ((j ^ s) << 1 | 1) & 31, h[i + 1] + a[i + 1] - d, i, 0}, maxx = max(maxx, h[i + 1] + a[i + 1] - d + Q); // debug("[%d][%d] = %d\n", j, ((j ^ s) << 1 | 1) & 31, h[i + 1] + a[i + 1] - d); if (!s) break; } for (int t = 0; t < (1 << k); t++) { if (g[t] == 2e9) continue; q[++lenq] = {j, t, g[t], i, 0}; maxx = max(maxx, g[t] + Q); } } for (int i = 1, l, r, x; i <= m; i++) { read(l, r, x); maxx = max(maxx, x + Q); q[++lenq] = {l, r, x, i, 1}; } build(1, 1, n - 1); for (int i = 1; i <= lenq; i++) cnt[q[i].x + Q]++; for (int i = 1; i <= maxx; i++) cnt[i] += cnt[i - 1]; for (int i = lenq; i >= 1; i--) qq[cnt[q[i].x + Q]--] = q[i]; for (int i = 1; i <= lenq; i++) { int l = qq[i].l, r = qq[i].r, x = qq[i].x, id = qq[i].id, typ = qq[i].typ; if (typ) { // Matrix f(32, bitset<32>(inf)); // f[0][0] = h[l] > x, f[0][1] = h[l] + a[l] > x; f.set(); f[0] = h[l] > x, f[1] = h[l] + a[l] > x; query(1, 1, n - 1, l, r - 1); ans[id] = !f[0]; } else { modify(id, l, r); } } for (int i = 1; i <= m; i++) puts(ans[i] ? "Yes" : "No"); } return 0; }:::
代码 6 期望得分 ,实际得分 。
代码 6 和正常编写的代码 5 相比重构太多,可读性比较差。
后记 & 闲话
:::info[后记 & 闲话]
为什么我要写这么详细的题解?
我个人是理解能力比较一般的人,每次看到模拟赛后贴出来的两三行题解,都得仔细研读,乃至去网上搜索更加详细的题解。这让我的改题十分痛苦,因为不能准确捕捉到写题解的人的意思。
我没有权利要求其他选手写出详细的题解,毕竟题解是选手花自己的时间分享自己的心得,而不是老师教导学生。但我想写详细的题解,真正能帮别人的学会这道题的题解,而不仅是自己的做题心得,而且详细的题解也能让我更好地梳理自己的思路,对题目有更深的认识。
居然每个部分都有代码?
这几个部分大致和出题人题解一样,我就是按着这几个部分一个部分一份代码写过来的。这也是正常、合理、科学的思考链条,按部就班有利于更好把握题目。
吐槽
这题花了我很多时间,毕竟每个部分我都去实现。但卡常花了我尤其久,有一天半左右了。总共提交了九十多发。卡常是真的恶心。题很好,就是卡常太恶心。
致谢
本题的出题人
https://www.luogu.com.cn/user/573341助,是一位实力强大且思路清晰的选手。非常感谢。闲话
洛谷更新的这个折叠框我非常喜欢。
这篇题解花了我很多时间,虽然这题是冷门题,看到的人也不多,但希望我的题解帮到了你。
这是我写过最久最长最详细的题解,27k+ 字。 :::
- 1
信息
- ID
- 11214
- 时间
- 1000~4000ms
- 内存
- 1024MiB
- 难度
- 6
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者