口胡赛
编者按:本文为笔者 2026/7/21 所打的口胡赛,按照笔者感知的难度排序。
E
给定 \(a_1 \sim a_n\), 求满足 \(a_i \le a_i \oplus a_j \le a_j\) 的有序对 \((i, j)\) 数。
我们发现,如果想让 \(x \le x \oplus y \le y\) 成立的充要条件是:
\(x\) 的第一个 \(1\) 所在的位置,必须位于 \(y\) 中非第一个 \(1\) 的 \(1\) 的位置上。
观察到这个结论后,我们就可以开一个桶,记录一下第一个 \(1\) 出现在位置 \(i\) 的有多少。
计算答案即可。
A
给定 \(K, M(1 \le K, M \le 10^{10})\),求有多少个 \(N \le M\) 满足 \(S(N) \% K = N \% K\)。
考虑到直接枚举 10 位显然不可取,发现可以将 10 位劈成两半,5 位 5 位地处理。
用 map<int, int> \(f[i]\) 存储 \(这 5 位数字和\% k - 这五位数 \% k = i\) 有多少个,用 map \(g[i]\) 存 \(这五位数字和\% k - 这五位数 * 10^5 \% k = i\) 有多少个,每次答案枚举 \(-10^5 \le i \le 10^5\) \(+= f[i] * g[-i] + f[i] * g[k - i]\)。对于边界情况特判处理即可。
时间 \(\mathcal O(\sqrt(n))\),可以证明,\(f\) 中有值的 \(i\) 一定是 \(\le 10^5\)。
H
给定一个 \(0 \sim 2n - 1\) 的排列,每次操作可以交换任意两个位置上的数,求最少需要多少次交换,使得对于所有 \(i \in [0,n−1]\),数 \(2i\) 和 \(2i+1\) 都相邻(不要求顺序)。
将给定的排列的 \(2i\) 和 \(2i + 1\) 项打包成一个节点,并对于其中的每个数向其要匹配的那一个数所在节点连边,最终易知形成的图包含若干环(自环也算环),最终答案为每个环的大小减一之和,也可以是 \(n - 环的数量\)。
解释具体见图:

B
给定一个 \(n\) 个点 \(m\) 条边的无向连通图(可能有重边,但无自环),以及 \(BFS\) 树。将边排序后按顺序建边跑 BFS,构造合法排列方案。
建树后,对于每一个点 \(u\),枚举与其连的边 \((u, v), v \ne fa[u]\),题目保证有解,故不存在 \(dep_{fa_v} < dep_{af_u}\)。 \(dep_{fa_v} > dep_{fa_u}\) 一定不会先走到没有影响,若 \(dep_{fa_u} = dep_{fa_v}\),则倍增查找 \(LCA(u, v)\),返回 \(LCA\) 和 \(LCA\) 分别向 \(u, v\) 走一步的节点 \(k_u\)、\(k_v\)(显然这可以在 \(LCA\) 内简单地求出)(容易发现 \(fa[k_u] = fa[k_v]\)),并在一个拓扑图上从 \(u\) 向 \(v\) 连边,表示 \((k_u, fa[u])\) 的连边在 \((k_v,fa[v])\) 的连边前必须输入。
处理完所有后, 在建出来的拓扑图上跑拓扑排序,跑出来的结果就是树上连边的排序情况,最后把所有非树边在最后输出即可。
C
一棵 \(n\) 个节点的树,点权 \(x_i\)。对于每条边,可选删或不删,共 \(2^{n - 1}\) 种方案,定义每种方案的权值为 \(\prod_{S = 这种方案里的连通块} \sum_{u \in S} x_u\) ,求所有权值之和。
Trick
异或是按位运算,考虑将异或按位拆开,设计状态,每位都可以独立计算。
我们定义:\(g_{u, j, 0/1}\) 表示在 \(u\) 的字数的所有删边方案中,当前块异或的第 \(j\) 位是否为 \(0 / 1\) 的所有方案乘积之和。
初始状态
对于每一个叶子节点 \(u\),都有 \(g_{u, i, (x_u >> j) \& 1} = 1\),即只有一种权值为 \(1\) 的有效拆分方案。
转移
设 \(u\) 的子节点是 \(v\),那么我们有两种情况:
-
删除此边
即将 \(v\) 和其他点单独分为一个连通块,此时会有:
\[g_{u, i, b} \xleftarrow{+} g_{u, j, b} \times f_v \] -
保留此边
合并 \(u\) 和 \(v\) 所在连通块,异或值异或。
则有:
\[g_{u, j, b\oplus c} \xleftarrow{+} g_{u, j, b} \times g_{v, j, c} \]
答案
对于每个节点 \(u\),答案 \(f_u = \sum\limits_{j = 0}^{60}2^j\cdot g_{u, j, 1} \mod 998244353\)。
答案就是 \(f_1\)。
D
维护一个长度为 \(T\) 的整数序列:
Q x y:求 \([x, y]\) 的当前最大值。A x y:求 \([x, y]\) 的历史最大值(包括现在)。P x y Z:将 \([x, y]\) 每个数增加 \(Z\)。C x y Z:将 \([x, y]\) 覆盖为 \(Z\)。
我们发现实际上我们并不好维护 \([x, y]\) 的历史最大值,无法很好地描述懒标记的下传。
矩阵乘法统一操作
我们使用矩阵乘法将所有操作统一为矩阵区间乘,避免懒标记的影响。
我们使用 \((max, +)\) 半环作为我们的矩阵。
初始矩阵:
考虑矩阵操作需要常数项,所以在此处我们添加一项常数,即加法单位元,作为我们的初始矩阵。
区间加 \(z\)
构造矩阵
区间覆盖 \(z\)
\(\begin{bmatrix}-\infty &-\infty &z \\ -\infty &0 &z \\ -\infty &-\infty &0\end{bmatrix}\)
使用完矩阵乘法优化后,我们发现,矩阵乘法的常数依旧很大,来到了 \(27\) 的级别。
考虑优化:
我们发现,矩阵中 \((1, 2), (2, 2), (3, 1), (3, 2), (3, 3)\) 是恒不变的,故我们考虑只维护剩下四个位置的情况,这样时间常数下降,来到了 \(4\)。
其他的东西就和普通的线段树差不多了,记录懒标记时,如果没有操作,则记 \(tag\) 为 \(单位矩阵 \; I\)。
上述做法就是矩阵优化线段树,主要用于懒标记维护多或者标记难以下传等问题,适配于查询历史信息等经典模型。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e5 + 5;
const int INF = 1e18;
const int NINF = -1e18;
int n, m;
int a[N];
int add(int x, int y) { return (x == NINF || y == NINF) ? NINF : x + y; }
struct Mat {
int a11, a13, a21, a23;
Mat() : a11(0), a13(NINF), a21(NINF), a23(NINF) {}
Mat(int _11, int _13, int _21, int _23) : a11(_11), a13(_13), a21(_21), a23(_23) {}
Mat operator * (const Mat& b) const {
Mat c;
c.a11 = add(a11, b.a11);
c.a13 = max(add(a11, b.a13), a13);
c.a21 = max(add(a21, b.a11), b.a21);
c.a23 = max(max(add(a21, b.a13), b.a23), a23);
return c;
}
};
namespace segtree {
#define ls (u << 1)
#define rs (u << 1 | 1)
#define mid ((l + r) >> 1)
#define segroot int u = 1, int l = 1, int r = n
#define lson ls, l, mid
#define rson rs, mid + 1, r
struct Node { int mx, his; Mat tag; } t[N*4];
void up(int u) { t[u].mx = max(t[ls].mx, t[rs].mx), t[u].his = max(t[ls].his, t[rs].his); }
void down(int u, const Mat& M) {
int mx = t[u].mx, his = t[u].his;
t[u].mx = max(add(M.a11, mx), M.a13);
t[u].his = max(max(add(M.a21, mx), his), M.a23);
t[u].tag = M * t[u].tag;
}
void down(int u) { down(ls, t[u].tag), down(rs, t[u].tag), t[u].tag = Mat(); }
void bld(segroot) {
t[u].tag = Mat();
if(l == r) { return t[u].mx = t[u].his = a[l], void(); }
bld(lson), bld(rson), up(u);
}
void mdf(int ql, int qr, const Mat& M, segroot) {
if(qr < l || r < ql) return;
if(ql <= l && r <= qr) { down(u, M); return; }
down(u);
mdf(ql, qr, M, lson), mdf(ql, qr, M, rson);
up(u);
}
pair<int,int> query(int ql, int qr, segroot) {
if(ql <= l && r <= qr) return {t[u].mx, t[u].his};
down(u);
if(qr <= mid) return query(ql, qr, lson);
if(ql > mid) return query(ql, qr, rson);
auto L = query(ql, qr, lson), R = query(ql, qr, rson);
return {max(L.first, R.first), max(L.second, R.second)};
}
}
using namespace segtree;
signed main() {
scanf("%lld", &n);
for(int i = 1; i <= n; ++i) scanf("%lld", &a[i]);
bld();
scanf("%lld", &m);
char op; int x, y, z;
while(m--) {
scanf(" %c", &op);
if(op == 'Q') {
scanf("%lld%lld", &x, &y);
printf("%lld\n", query(x, y).first);
} else if(op == 'A') {
scanf("%lld%lld", &x, &y);
printf("%lld\n", query(x, y).second);
} else if(op == 'P') {
scanf("%lld%lld%lld", &x, &y, &z);
mdf(x, y, Mat(z, NINF, z, NINF));
} else if(op == 'C') {
scanf("%lld%lld%lld", &x, &y, &z);
mdf(x, y, Mat(NINF, z, NINF, z));
}
}
return 0;
}
F
给定一个 DAG,有恰好一个汇点,每个点有一个非负整数边权。每一个时刻,对于 \(\forall x\) 满足 \(a_x > 0\) 都有 \(a_x - 1\), 同时 \(\forall y\; (x \to y) \space a_y +1\)。求多少时刻后,\(\forall a_i = 1\),对 \(998,244,353\) 取模。
我们发现,若不存在 \(a_x = 0\) 且 \(\exists y:(y \to x)\) 的点,那么所有的点都可以通过一个**拓扑图上 dp **的方法解决。
# 伪代码
for u in topu:
for v in to[u]:
a[v] += a[u]
那么对于如果上面那种截断的点怎么办呢?
我们可以发现,图中存在的最长链长度不超过 \(n - 1\),这就说明,至多 \(n\) 秒以后,所有初始非零流量一定到达汇点。所以此时,我们直接暴力 \(\mathcal O(n\cdot (n+m))\) 模拟前 \(n\) 秒即可。
如果此时已经达到要求,直接输出,未达到要求就跑一边上面 dp,即可求出答案。
总时间复杂度为 \(\mathcal O(n \cdot (n + m))\)。
G
给定 \(n\) 个按照字典序排好的字符串(字符串内部也已按字母从小到大排好)。定义函数 \(f\) 为对于任意的字符串序列 \([t1, t2, \cdots, t_m]\) 至少是其中至少一个字符串的子序列的不同字符串(含空串)的个数。
对于所有 \(2^n\) 个子序列求 \(f(S)\) ,乘 \(|S| \cdot \sum_{i \in S} i\) ,后对 \(998,244,353\) 取模,异或和的值。
我们发现,可以考虑容斥原理:
这里 \(g(T)\) 表示至少是 \(T\) 的子序列的字符串数。我们发现,这里 \(g(T)\) 也好求:
而对于这个 \(g(T)\),我们可以很好的使用子集 DP 计算:
我们记 \(mn_c[S] = min(mn_c[S \backslash lowbit], cnt_{bit, c})\)。
那么就有:\(g[S] = \prod\limits_{c}(mn_c[S] + 1) \mod 998244353\)。
时间复杂度 \(\mathcal O(n \cdot2^n)\)。
接下来使用高维前缀和即可求出 \(f\) 的值。

浙公网安备 33010602011771号