AtCoder Beginner Contest 367
题目传送门:AtCoder Beginner Contest 367。
A - Shout Everyday
题意简述(*43)
每天你都在 \(b\) 点钟时睡觉,在 \(c\) 点钟时起床。问你在 \(a\) 点钟时是否醒着。时间采用 24 小时制,且三个时间点互不相同。
数据范围:\(0 \le a, b, c < 24\) 且 \(a, b, c\) 两两不同。
AC 代码
#include <iostream>
using std::cin, std::cout;
void Solve();
int main() {
cin.sync_with_stdio(false);
cin.tie(nullptr);
Solve();
return 0;
}
void Solve() {
int a, b, c;
cin >> a >> b >> c;
if (b < c) b += 24;
if (a < c) a += 24;
cout << (c <= a && a <= b ? "Yes" : "No") << '\n';
}
关键在于:
- 若 \(b < c\),则你在零点钟时还醒着,故醒着的时段是一天中的前缀 \([0, b]\) 和后缀 \([c, 24]\)。
- 若 \(b > c\),则你在零点钟时睡着了,故醒着的时段是一天中的一段区间 \([c, b]\)。
- 可以对这两种情况分别进行处理。
通过以 \(c\)(起床时刻)为基准,将 \(a, b\) 当小于 \(c\) 的时候加上 \(24\),总可以将判断转化为判断 \(a\) 是否在区间 \([c, b]\) 内的情况。
加上 \(24\) 了之后相当于 \(25\) 点钟相当于次日的凌晨 \(1\) 点钟。
感性理解:在日本的广播业和深夜营业的店铺中常用的 30 小时制时刻表示法。
时空复杂度为 \(\mathcal O(1)\)。
B - Cut .0
题意简述(*43)
以恰好精确到小数点后第三位的形式输入一个实数 \(x\),请你去掉不必要的末尾零以及小数点后输出。
数据范围:\(0 \le x < 100\) 且 \(x\) 以恰好精确到小数点后第三位的形式给出。
AC 代码
#include <iostream>
using std::cin, std::cout;
void Solve();
int main() {
cin.sync_with_stdio(false);
cin.tie(nullptr);
Solve();
return 0;
}
void Solve() {
double x;
cin >> x;
cout << x << '\n';
}
由于 C++ 的 std::ostream
的特质,在默认初始化后输出一个浮点数,会在保留 6 位有效位数后,(如果不触发科学计数法格式)自动进行末尾零和小数点的移除。再由于 \(x < 100\),即有效位数不足 5,且 \(x\) 的指数不会过小以至于触发科学计数法格式,故而在输入后,直接 std::cout << x << '\n';
即可达成目的。
使用 std::printf("%g", x);
可以达成完全一致的目的。
具体而言,是因为 std::ostream
输出浮点数时使用了函数 std::num_put::put
进行,它调用了虚函数 std::num_put::do_put
,在处理浮点数时,虚函数默认的实现,当 std::ios_base
的格式化标志(即 std::ios_base::flags()
)为默认设置(即仅通过调用函数 std::basic_ios::init
初始化后未改动)时,为如同以 %g
的转换说明符进行 C 风格输出得到的结果。
详细来说,格式化标志中的 std::ios_base::floatfield
管理浮点数的格式化方式,其中含有 std::ios_base::fixed
和 std::ios_base::scientific
两个标志,默认这两个标志都没有开启。当使用 std::fixed
开启后,行为就如同以 %f
的转换说明符进行 C 风格输出了。当然还有我们很熟悉的 std::setprecision
可以对精度进行调整(默认值是 6)。
时空复杂度为 \(\mathcal O(1)\)。
C - Enumerate Sequences
题意简述(*234)
给定长度为 \(n\) 的正整数序列 \((r_1, \ldots, r_n)\) 和正整数 \(k\)。
以字典序从小到大的顺序输出所有满足 \(1 \le a_i \le r_i\) 且 \(a_1 + \cdots + a_n\) 是 \(k\) 的倍数的长度为 \(n\) 的整数序列 \((a_1, \ldots, a_n)\)。
数据范围:\(n \le 8\),\(1 \le r_i \le 5\),\(2 \le k \le 10\)。
AC 代码
#include <iostream>
using std::cin, std::cout;
#define F(i, a, b) for(int i = a; i <= (b); ++i)
#define F2(i, a, b) for(int i = a; i < (b); ++i)
void Solve();
int main() {
cin.sync_with_stdio(false);
cin.tie(nullptr);
Solve();
return 0;
}
const int MN = 8;
int n, k, r[MN];
int a[MN];
void DFS(int s) {
if (s == n) {
int sum = 0;
F2(i, 0, n)
sum += a[i];
if (sum % k == 0)
F2(i, 0, n)
cout << a[i] << " \n"[i + 1 == n];
return ;
}
F(v, 1, r[s]) {
a[s] = v;
DFS(s + 1);
}
}
void Solve() {
cin >> n >> k;
F2(i, 0, n)
cin >> r[i];
DFS(0);
}
先不谈总和要是 \(k\) 的倍数的条件,序列的总数只有 \(\prod_{i = 1}^n r_i \le (\max r)^n \le 5^8 \le 39{,}0625\) 那么多,所以把所有序列列举出来在时间上是可以接受的。
另一方面讲,\(k\) 最大是 \(10\),大约只能排除掉九成的序列而还要留下一成,所以序列总数必然不能过多。
对于按字典序输出的要求,一个 DFS(深度优先搜索)足矣。我们需要在 DFS 到达边界后求和进行判断和输出。
时间复杂度为 \(\mathcal O((\max r)^n \cdot n)\),空间复杂度为 \(\mathcal O(n)\)。
D - Pedometer
题意简述(*1037)
环上有 \(n\) 个关键点,按顺时针以 \(1 \sim n\) 编号。
给定正整数 \(a_1, \ldots, a_n\)。从点 \(i\) 顺时针走 \(a_i\) 的距离才能到达顺时针方向的下一个点。
给定 \(m\),求满足从点 \(i\) 顺时针走到点 \(j\) 的距离为 \(m\) 的倍数的点对 \((i, j)\)(要求 \(i \ne j\))的数量。
数据范围:\(n \le 10^5\),\(a_i \le 10^9\),\(m \le 10^6\)。
AC 代码
#include <iostream>
#include <vector>
using std::cin, std::cout;
#define F2(i, a, b) for(int i = a; i < (b); ++i)
void Solve();
int main() {
cin.sync_with_stdio(false);
cin.tie(nullptr);
Solve();
return 0;
}
using LL = long long;
void Solve() {
int n, m;
cin >> n >> m;
std::vector<int> a(n);
F2(i, 0, n)
cin >> a[i];
std::vector<int> b(m, 0);
int s = 0;
F2(i, 0, n) {
s = (s + a[i]) % m;
++b[s];
}
int t = s;
LL ans = 0;
F2(i, 0, n) {
s = (s + a[i]) % m;
++b[s];
--b[(s - t + m) % m];
ans += b[s] - 1;
}
cout << ans << '\n';
}
考虑断环为链,变为 \(2 n\) 个点,前 \(n\) 个只作辅助用,后 \(n\) 个作为关心的终点。记录下这些点的坐标(即从原点出发到达它们需要的距离)。
要问从多少个点出发到达点 \(i\) 的距离为 \(m\) 的倍数,就是在问从 \(i\) 往前数 \(n - 1\) 个点(不包含 \(i\) 本身),这些点中坐标与 \(i\) 的坐标模 \(m\) 同余的点的数量。
使用一个值域为 \([0, m)\) 的桶来统计余数,终点每向右移动一次时,就在桶中添加它当前坐标的余数,并删除它在绕一圈前坐标的余数。将桶中与当前相同的余数数量(但要减去 \(1\),因为本身不算)累加就得到答案。
时空复杂度为 \(\mathcal O(n + m)\)。
E - Permute K times
题意简述(*1370)
给定两个长度为 \(n\) 的整数序列 \((a_1, \ldots, a_n)\) 和 \((x_1, \ldots, x_n)\),其中保证 \(1 \le x_i \le n\)。
求经过以下变换 \(k\) 次后的序列 \(a\):
- 将 \(a\) 替换为 \(b\),其中序列 \((b_1, \ldots, b_n)\) 定义为 \(b_i = a_{x_i}\)。
数据范围:\(n \le 2 \times 10^5\),\(k \le 10^{18}\),\(1 \le x_i \le n\)。
AC 代码
#include <iostream>
#include <vector>
using std::cin, std::cout;
#define F(i, a, b) for(int i = a; i <= (b); ++i)
void Solve();
int main() {
cin.sync_with_stdio(false);
cin.tie(nullptr);
Solve();
return 0;
}
using LL = long long;
void Solve() {
int n;
LL k;
cin >> n >> k;
std::vector<int> x[60], a(n + 1);
x[0].resize(n + 1);
F(i, 1, n)
cin >> x[0][i];
F(j, 0, 58) {
x[j + 1].resize(n + 1);
F(i, 1, n)
x[j + 1][i] = x[j][x[j][i]];
}
F(i, 1, n)
cin >> a[i];
F(i, 1, n) {
int p = i;
F(j, 0, 59)
if (k >> j & 1)
p = x[j][p];
cout << a[p] << " \n"[i == n];
}
}
注意到最后 \(a\) 的每个元素都是原先 \(a\) 的某个元素。若能快速求得变换 \(k\) 次后的 \(a\) 的第 \(i\) 位是原先 \(a\) 的第几位,则本题也迎刃而解。
- 一次后,第 \(i\) 位是原先的第 \(x_i\) 位。
- 两次后,第 \(i\) 位是原先的第 \(x_{x_i}\) 位。
- ……
把 \(1 \sim n\) 看作一张图中的结点,以每个 \(i\) 向对应的 \(x_i\) 连一条有向边,每个点恰好有一条出边。
可以看出,设从点 \(i\) 出发沿着出边走 \(k\) 次后到达点 \(p\),则最后的第 \(i\) 位就是原先的第 \(p\) 位。
一张每个点恰好有一条出边的有向图是一片基环内向森林。至此本题被转化为基环内向森林上的“\(k\) 级祖先”问题。
可以通过倍增解决 \(k\) 级祖先问题:设 \(g(j, i)\) 表示从点 \(i\) 出发沿着出边走 \(2^j\) 次后到达的点,则有边界 \(g(0, i) = x_i\) 和转移 \(g(j + 1, i) = g(j, g(j, i))\)。要回答询问,只需将 \(k\) 进行二进制拆分,后对每个进制位 \(j\) 进行 \(p \gets g(j, p)\) 的跳跃即可。
时空复杂度为 \(\mathcal O(n \log k)\)。
注:由于这里的 \(k\) 级祖先可以离线询问,存在线性时空的做法。具体而言,找到每个连通块的环后进行 DFS,记录 DFS 栈,然后定位栈上从当前点往前数 \(k\) 位的点,或栈长不足 \(k\) 时定位环上的特定点。由于代码实现比倍增的做法复杂,不展示具体代码。
F - Rearrange Query
题意简述(*1540)
给定两个长度为 \(n\) 的序列 \((a_1, \ldots, a_n)\) 和 \((b_1, \ldots, b_n)\)。
回答 \(q\) 次询问,每次给定 \(l, r, L, R\)(保证 \(1 \le l \le r \le n\) 且 \(1 \le L \le R \le n\)),判断是否可以通过重排将 \(a\) 的子串 \((a_l, \ldots, a_r)\) 变为 \(b\) 的子串 \((b_L, \ldots, b_R)\)。
数据范围:\(n, q \le 2 \times 10^5\),\(1 \le a_i, b_i \le n\)。
AC 代码
#include <iostream>
#include <vector>
#include <random>
using std::cin, std::cout;
#define F(i, a, b) for(int i = a; i <= (b); ++i)
void Solve();
int main() {
cin.sync_with_stdio(false);
cin.tie(nullptr);
Solve();
return 0;
}
using ULL = unsigned long long;
void Solve() {
int n, q;
cin >> n >> q;
std::vector<int> a(n + 1), b(n + 1);
F(i, 1, n)
cin >> a[i];
F(i, 1, n)
cin >> b[i];
std::mt19937_64 rng(0x12345);
std::vector<ULL> g(n + 1);
F(i, 1, n)
g[i] = rng();
std::vector<ULL> sa(n + 1), sb(n + 1);
sa[0] = sb[0] = 0;
F(i, 1, n) {
sa[i] = sa[i - 1] + g[a[i]];
sb[i] = sb[i - 1] + g[b[i]];
}
F(q_, 1, q) {
int l, r, L, R;
cin >> l >> r >> L >> R;
ULL v = sa[r] - sa[l - 1] - sb[R] + sb[L - 1];
cout << (v ? "No" : "Yes") << '\n';
}
}
也就是判断子串 \(a[l:r]\) 和 \(b[L:R]\) 作为多重集是否相等。
一个熟知的判断多重集相等的办法是散列。即将多重集映射为一个数,以数的相等代多重集的相等,这个数称作散列值。为了确保正确性,需要在映射过程中引入随机性,并通过分析证明错误概率极小。
适用于本题的散列方式是令多重集 \(S = \{ s_1, \ldots, s_k \}\) 映射为 \(\bigl( \sum_{i = 1}^{k} g(s_i) \bigr) \bmod m\)。其中 \(g\) 为一个定义域为 \(s\) 的取值范围,值域为 \([0, m)\) 的均匀随机函数,即所有 \(g(s)\) 对于不同的 \(s\) 来说都是独立的随机变量。
分析:
- 如果两多重集 \(S, T\) 相同,则它们通过散列被映射到的数也相同。
- 如果两多重集 \(S, T\) 不同,则它们的对称差 \(D = S \mathbin{\Delta} T\) 非空。
- 同时,作差得到 \(S' = S \setminus T\) 和 \(T' = T \setminus S\),此时有 \(S'\) 与 \(T'\) 无交且 \(D = S' \cup T'\)。
- 此时 \(S, T\) 的散列值相同当且仅当 \(S', T'\) 的散列值相同。
- 设 \(S'\) 中有 \(k\) 种数值 \((s_1, \ldots, s_k)\) 分别出现了 \((c_1, \ldots, c_k)\) 次;
- 设 \(T'\) 中有 \(l\) 种数值 \((t_1, \ldots, t_l)\) 分别出现了 \((d_1, \ldots, d_l)\) 次;
- 且 \(k + l \ge 1\),即 \(S' \cup T' = D\) 非空。
- 则 \(S', T'\) 的散列值相同等价于 \(\sum_{i = 1}^{k} c_i \cdot g(s_i) + \sum_{i = 1}^{l} (-d_i) \cdot g(t_i) \equiv 0 \pmod{m}\)。
- 根据 \(k + l \ge 1\),求和式中至少包含一项,设为 \(C \cdot g(V)\),即该项的 \(c_i\) 或 \(-d_i\) 记作 \(C\) 而 \(s_i\) 或 \(t_i\) 记作 \(V\)。
- 有 \(g(V)\) 是在 \([0, m)\) 中均匀取值的随机变量,则(根据熟知数论知识)该项取任意特定值的概率最大不会超过 \(\gcd(C, m) / m\)。
- 故 \(S, T\) 的散列值相同的概率不超过 \(\gcd(C, m) / m\),其中 \(C\) 可以在 \(\lvert C \rvert \le n\) 且 \(C \ne 0\) 的条件下取“最坏”的值。
其中 \(C\) 最坏的值可以取到 \(n\) 以内 \(m\) 的最大因数。
一种最优的选择是取 \(\bm{m = p}\) 为素数,从而只要 \(n < p\),错误率就不超过 \(1 / p\)。
回看散列函数 \(S \mapsto \bigl( \sum_{i = 1}^{k} g(s_i) \bigr) \bmod m\),当作用于序列 \(a, b\) 的区间上时,可以用前缀和的手法处理:令 \(\Sigma a_i = \bigl( \sum_{j = 1}^{i} g(a_j) \bigr) \bmod m\),则 \(a[l:r]\) 作为多重集的散列值为 \((\Sigma a_r - \Sigma a_{l - 1}) \bmod m\)。对序列 \(b\) 的处理同理。
代码实现上,可以取 \(m = 2^{64}\),使用 unsigned long long
进行计算,并使用 C++ 中的伪随机数生成器 std::mt19937_64
来生成 \(g\) 的取值。
- 此时单次错误率不超过 \(n / m\),故总错误率不超过 \(q n / m\)。取 \(m = \Omega(\epsilon^{-1} q n)\) 即可达到 \(\mathcal O(\epsilon)\) 的错误率要求。
- 计算可知,在 \(m = 2^{64}\) 的设置下,单测试点错误率不超过 \(3 \times 10^{-9}\)。
时间复杂度为 \(\mathcal O(n + q)\)。
注:存在其他多种不同的散列方式。
G - Sum of (XOR^K or 0)
题意简述(*3110)
给定长度为 \(n\) 的值域在 \([0, 2^b)\) 内的非负整数序列 \((a_1, \ldots, a_n)\) 和正整数 \(m, k\)。求
数据范围:\(n \le 2 \times 10^5\),\(a_i < 2^b\),\(b \le 20\),\(m \le 100\),\(1 \le k \le 2 \times 10^5\)。
AC 代码
代码中逆 Walsh 变换时没有除以 \(2^b\),改为在输出答案前除。
#include <iostream>
#include <vector>
using std::cin, std::cout;
#define F(i, a, b) for(int i = a; i <= (b); ++i)
#define F2(i, a, b) for(int i = a; i < (b); ++i)
void Solve();
int main() {
cin.sync_with_stdio(false);
cin.tie(nullptr);
Solve();
return 0;
}
using LL = long long;
const int Mod = (119 << 23) + 1;
const int B = 20, V = 1 << B, iV = Mod - ((Mod - 1) >> B);
int qPow(int b, int e) {
int a = 1;
for (; e; e >>= 1, b = (int)((LL)b * b % Mod))
if (e & 1)
a = (int)((LL)a * b % Mod);
return a;
}
void Solve() {
int n, m, k;
cin >> n >> m >> k;
std::vector<int> v(n + 1, 0); /* to build v */ {
std::vector<std::vector<int>> plus(n + 1, std::vector<int>(m, 0));
auto minus = plus;
plus[0][0] = minus[0][0] = 1;
F(i, 1, n)
F2(j, 0, m) {
int j2 = j ? j - 1 : m - 1;
plus[i][j] = (plus[i - 1][j] + plus[i - 1][j2]) % Mod;
minus[i][j] = (minus[i - 1][j] - minus[i - 1][j2] + Mod) % Mod;
}
F(i, 0, n)
F2(j, 0, m)
v[i] = (int)((v[i] + (LL)plus[i][j] * minus[n - i][j ? m - j : 0]) % Mod);
}
std::vector<int> f(V, 0);
F(i, 1, n) {
int a;
cin >> a;
++f[a];
}
F2(j, 0, B)
F2(i, 0, V)
if (~i >> j & 1) {
int x = f[i];
int& y = f[i | 1 << j];
f[i] = x + y;
y = x - y;
}
F2(i, 0, V)
f[i] = v[(f[i] + n) / 2];
F2(j, 0, B)
F2(i, 0, V)
if (~i >> j & 1) {
int x = f[i];
int& y = f[i | 1 << j];
f[i] = (x + y) % Mod;
y = (x - y + Mod) % Mod;
}
int ans = 0;
F2(i, 0, V)
if (f[i])
ans = (int)((ans + (LL)f[i] * qPow(i, k)) % Mod);
ans = (int)((LL)ans * iV % Mod);
cout << ans << '\n';
}
由于 \(b \le 20\),考虑 \(\tilde{\mathcal O}(2^b)\) 的做法是自然的。
如果可以对于每个值 \(v \in [0, 2^b)\) 求出有多少个满足限制的 \(S\) 使得 \(\displaystyle \bigoplus_{i \in S} a_i\) 就等于 \(v\),不妨记这样的 \(S\) 的数量为 \(f_v\),则答案为 \(\displaystyle \Biggl( \sum_{v = 0}^{2^b - 1} (f_v - [v = 0]) \cdot v^k \Biggr) \bmod 998244353\)。
(减去 \([v = 0]\) 是因为要扣除 \(S\) 为空集的情况,其恰好对应 \(v = 0\)。不过本题保证 \(k \ge 1\),因此 \(v = 0\) 时 \(v^k = 0\),所以不管也行。)
只需求出所有 \(f_v \bmod 998244353\) 即可。本题没有需要讨论数论性质的部分(除了要除以 \(2^b\)),故后文中省略对模 \(998244353\) 的讨论。
有 \(\displaystyle f_v = [x^v y^0] \prod_{i = 1}^{n} (1 + x^{a_i} y)\),其中元 \(x\) 进行异或卷积,元 \(y\) 进行长度为 \(\bm m\) 的循环卷积。
可以对 \(x\) 的维度进行 Walsh 变换将异或卷积转换为点积,但由于缺乏 \(m\) 次单位根 \(\omega_m\),保留 \(y\) 的维度进行普通的循环卷积。这里我们的底气是 \(m \le 100\) 范围不大。
我们知道 \(1\)(即 \(x^0\),仅有 \(0\) 处的值非零且为 \(1\) 的序列)的 Walsh 变换为全 \(1\) 序列。设 \(x^a\)(即仅有 \(a\) 处的值非零且为 \(1\) 的序列)的 Walsh 变换为序列 \(({h^a}_w)\),这里每个 \({h^a}_w\) 均为 \(1\) 或 \(-1\)。根据 Walsh 变换的线性性,一个因子 \((1 + x^a y)\) 的 Walsh 变换就为序列 \(({g^a}_w)\),其中 \({g^a}_w = 1 + {h^a}_w y\)。
于是将每个因子的序列 \(({g^a}_w)\) 全部点积起来后再取 \(y^0\) 次项,就得到 \((f_v)\) 的 Walsh 变换 \(\mathcal W f\)。可知 \(\displaystyle \mathcal W f_w = [y^0] \prod_{i = 1}^{n} (1 + {h^{a_i}}_w y)\),由于每个 \({h^{a_i}}_w\) 均为 \(1\) 或 \(-1\),简化为 \(\mathcal W f_w = [y^0] (1 + y)^{c_w} (1 - y)^{n - c_w}\),其中 \(c_w\) 为满足 \({h^{a_i}}_w = 1\) 的 \(i\) 的数量。
问题来到如何对每个 \(w\) 计算 \(c_w\),即满足 \({h^{a_i}}_w = 1\) 的 \(i\) 的数量。由于每个 \({h^{a_i}}_w\) 均为 \(1\) 或 \(-1\),有 \(\displaystyle \sum_{i = 1}^{n} {h^{a_i}}_w = c_w \cdot (1) + (n - c_w) \cdot (-1) = 2 c_w - n\),逆用此式即得 \(\displaystyle c_w = \frac{1}{2} \Biggl( n + \sum_{i = 1}^{n} {h^{a_i}}_w \Biggr)\)。
根据 Walsh 变换的线性性,既然 \(({h^{a_i}}_w)\) 是 \(x^{a_i}\) 的 Walsh 变换,就有 \(\displaystyle \Biggl( \sum_{i = 1}^{n} {h^{a_i}}_w \Biggr)\) 是 \(\displaystyle \sum_{i = 1}^{n} x^{a_i}\) 的 Walsh 变换。于是只需对 \(\displaystyle \sum_{i = 1}^{n} x^{a_i}\) 进行一次快速 Walsh 变换即可求得所有的 \(c_w\)。
接下来解决已知 \(c_w\) 求 \(\mathcal W f_w = [y^0] (1 + y)^{c_w} (1 - y)^{n - c_w}\) 的部分。注意 \(m \le 100\) 的范围使得 \(\mathcal O(n m)\) 的复杂度是可以接受的,可以直接对每个 \(0 \le i \le n\) 预处理出每个 \((1 + y)^i\) 和 \((1 - y)^i\)(注意是循环卷积,所以长度只有 \(m\)),然后就有 \(\displaystyle [y^0] (1 + y)^c (1 - y)^{n - c} = \sum_{j = 0}^{m - 1} \bigl( [y^j] (1 + y)^c \bigr) \cdot \bigl( [y^{m - j}] (1 - y)^{n - c} \bigr)\)。这部分的时空复杂度均为 \(\mathcal O(n m)\)。
最后,在求得 \((\mathcal W f_w)\) 后,将 \(\mathcal W f\) 进行一次快速逆 Walsh 变换即得 \((f_v)\),直接计算 \(\displaystyle \sum_{v = 0}^{2^b - 1} f_v \cdot v^k\) 即可。
时间复杂度为 \(\mathcal O(n m + 2^b (b + \log k))\),空间复杂度为 \(\mathcal O(n m + 2^b)\)。其中 \(\log k\) 来自求 \(v^k\) 时使用的快速幂。
注:
- 熟知快速 Walsh 变换的时间复杂度为 \(\mathcal O(2^b b)\)。
- 求 \(v^k\) 时可以使用线性筛法将 \(\mathcal O(2^b \log k)\) 变为 \(\mathcal O(2^b b^{-1} \log k)\)。
- 延伸阅读:
- UOJ NOI Round #2 Day1 C. 黎明前的巧克力(UOJ310);
- IOI 2023 集训队互测 Round 8 A. 环覆盖(QOJ5089);
- Codeforces Global Round 2 H. Triple(CF1119H)。
后记:我本来想的线性基,在提示做法涉及 FWT 后想出来了,所以总体上算独立做出的。