口胡赛

编者按:本文为笔者 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, +)\) 半环作为我们的矩阵。

初始矩阵:

\[\begin{bmatrix} mx \\ his \\ 0 \end{bmatrix} \]

考虑矩阵操作需要常数项,所以在此处我们添加一项常数,即加法单位元,作为我们的初始矩阵。

区间加 \(z\)

构造矩阵

\[\begin{bmatrix} z & -\infty & -\infty \\ z & 0 & -\infty \\-\infty & -\infty & 0\end{bmatrix} \]

区间覆盖 \(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\) 取模,异或和的值。

我们发现,可以考虑容斥原理:

\[f(S) = \sum\limits_{\phi \ne T \subseteq S} (-1)^{|T|+1}g(T) \]

这里 \(g(T)\) 表示至少是 \(T\) 的子序列的字符串数。我们发现,这里 \(g(T)\) 也好求:

\[g(T) = \prod\limits_{c = 0}^{25}(\min\limits_{i \in T} cnt_{i ,c} + 1) \]

而对于这个 \(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\) 的值。

posted @ 2026-07-21 16:47  Hty111  阅读(4)  评论(1)    收藏  举报