目录

    2026 年 ACM 暑假集训

    2026 年 ACM 暑假集训

    2026 牛客暑期多校训练营 1

    F. Permutation Generation

    给定 \(n \in \mathbb{N}^*\),记 \(N = \mathbb{N} \cap [0, n)\)。 对于 \(N\) 的一个排列 \(p = (p_i)_{i = 0}^{n - 1}\),记

    \[f(p) = \sum_{i = 0}^{n - 1} \sum_{j = i + 1}^{n - 1}(p_j - p_i). \]

    现在给出 \(N\) 的一个排列 \(p\)\(k,v \in N\),你需要求出 \(N\) 的一个排列 \(q = (q_i)_{i = 0}^{n - 1}\),使得以下条件都成立:

    • \(q_k = v\)
    • \(f(p) \equiv f(q) \pmod{n}\)

    或报告无解。

    保证 \(n \leq 2 \times 10^5\)

    \(f\) 是容易化简的:

    \[f(p) = \sum_{i = 0}^{n - 1} p_i (i - (n - 1 - i)) = \sum_{i = 0}^{n - 1} p_i (2i - n + 1). \]

    如果再利用上 \(p\) 是排列这一性质的话,还可以进一步化简为

    \[f(p) = 2 \sum_{i = 0}^{n - 1} p_i i - \dfrac{1}{2} n (n - 1)^2. \]

    我和一位队友都注意到,如果交换 \(p\) 的第 \(x\) 和第 \(y\) 项得到 \(p'\),会使其函数值变化

    \[f(p') - f(p) = -2 (y - x) (p_y - p_x). \]

    因此一直在考虑,要么从 \(p\) 交换出 \(q_k = v\) 后再想办法消除其影响,或直接从顺序交换出 \(q_k = v\) 后再凑出 \(f(q) \equiv f(p) \pmod{n}\)。我甚么办法也没想出来,队友倒是搞出了一种,但没有覆盖到所有情况,导致 WA。

    后来我们通过枚举小的数据,观察到都是有解的,因此猜测必定有解,但没有甚么用。

    当另一位队友终于写完 J 题大模拟后再看此题时,观察枚举的数据时注意到每个 \(n\) 都有答案可以通过 circular shift(循环移动?)得到,遂出正解。

    这里证明一下正确性:设将 \(p\) 循环左移一位得到 \(p'\),有

    \[\begin{align} f(p') & = \sum_{i = 0}^{n - 1} p_i (2(i - 1) - n + 1) + 2np_0 \\ & = \sum_{i = 0}^{n - 1} p_i (2i - n + 1) - 2 \sum_{i = 0}^{n - 1} p_i + 2 n p_0 \\ & = f(p) - n (n - 1) + 2 n p_0 \\ & \equiv f(p) \pmod{n}. \end{align} \]

    所以只需通过循环移动使第 \(k\) 位为 \(v\) 即可。时间复杂度 \(O(1)\)

    后来 Gemini 告诉我们,在出现 \(\bmod n\) 时应当优先考虑 circular shift。这是我们没有经验。

    inline void solve() {
    	uint32_t n, idx, val;
    	std::cin >> n >> idx >> val;
    	std::vector<uint32_t> perm(n);
    	uint32_t pos;
    	for (uint32_t i = 0; i < n; ++i) {
    		std::cin >> perm[i];
    		if (perm[i] == val) pos = i;
    	}
    	uint32_t lsh = pos - idx + n;
    	if (lsh >= n) lsh -= n;
    	for (uint32_t i = lsh; i < n; ++i) {
    		std::cout << perm[i] << ' ';
    	}
    	for (size_t i = 0; i < lsh; ++i) {
    		std::cout << perm[i] << ' ';
    	}
    	std::cout << '\n';
    }
    

    G. Precision Error?!

    给定 \(n \in \mathbb{N}^*\),需要找出一个点集 \(S \subset \mathbb{R}^3\) 使得

    • \(|S| \leq 2 n + 2\)
    • \((\forall P \in S)\ (|\{Q \in S: \Vert\vec{PQ}\Vert_2 = 1\}| = n)\)

    为了允许浮点误差,取精度 \(\epsilon = 0.01\)\(S\) 需要满足

    • \((\forall P,Q \in S)\ (P = Q \lor \Vert\vec{PQ}\Vert_2 > \epsilon)\)
    • \(|S| \leq 2 n + 2\)
    • \((\forall P \in S)\ (|\{Q \in S: \Vert\vec{PQ}\Vert_2 \in (1 - \epsilon, 1 + \epsilon)\}| = n)\)

    保证 \(n \leq 100\)。有 \(T \leq 100\) 组测试。

    看到题解真气笑了。我就说这个 \(\epsilon\) 怎么这么大,别的题都是 \(10^{-5}\)\(10^{-6}\),这里来个 \(10^{-2}\),他妈的,果然有蹊跷。

    注意到这里的 \(\epsilon\) 相当大,所以我们可以通过构造两部分点,使得同一部分的点之间非常近但不比 \(\epsilon\) 近,而不同部分的点之间的距离都几乎为 \(1\)

    对于平面 \(z = 0\) 上的一个点,在平面 \(z = 1\) 中有一个半径为 \(r = \sqrt{(1 + \epsilon)^2 - 1^2} \approx 0.1418 > 14 \epsilon\) 的圆,这个圆内的点到该点的距离都在 \([1, 1 + \epsilon)\)。这实际上是因为 \(\sqrt{1 + \left(\sqrt{2 \epsilon}\right)^2} = 1 + \epsilon + o(\epsilon^2)\),即圆的半径 \(r \approx \sqrt{2 \epsilon}\)。我们可以在两个平面上各放 \(n\) 个点。为了让两个平面的点互相在对方的容忍范围中,我们需要使它们都位于圆柱 \(x^2 + y^2 \leq \left(\dfrac{1}{2} r\right)^2\) 内(当然,圆心位置实际上是任意的)。

    简便起见,考虑将每个平面的点以正方形点阵排布,即用一个 \(\left\lceil \sqrt{n}\right\rceil \times \left\lceil \sqrt{n}\right\rceil\) 点阵排布这 \(n\) 个点。这样的正方形点阵中,设两个点的最近距离(即相邻两点的距离)为 \(d\),则两个点的最远距离为 \(\left(\left\lceil \sqrt{n}\right\rceil - 1\right) \cdot \sqrt{2} d\)。我们希望

    \[\begin{cases} d > \epsilon && \text{不重合} \\ \left(\left\lceil \sqrt{n}\right\rceil - 1\right) \cdot \sqrt{2} d < 2 \cdot \dfrac{1}{2} r && \text{在圆内} \end{cases}, \]

    解该不等式组得

    \[d \in \left(\epsilon, \dfrac{r}{\sqrt{2} \left(\left\lceil \sqrt{n}\right\rceil - 1\right)}\right) \]

    \(n \leq 100\) 得上界

    \[\dfrac{r}{\sqrt{2} \left(\left\lceil \sqrt{n}\right\rceil - 1\right)} \geq \dfrac{r}{9\sqrt{2}} \approx 0.01114 > 1.1 \epsilon, \]

    所以完全是可以的。

    constexpr double D = 0.011;
    constexpr size_t N = 100, S = 10; // sqrt(N)
    
    inline void preprocess() {
    	std::cout << std::fixed << std::setprecision(9);
    }
    
    inline void solve() {
    	size_t n;
    	std::cin >> n;
    	std::cout << (n << 1) << '\n';
    	for (size_t i = 0; i < n; ++i) {
    		auto [x, y] = std::div(SC<long long>(i), SC<long long>(S));
    		for (int z = 0; z < 2; ++z) {
    			std::cout << (D * x) << ' ' << (D * y) << ' ' << z << '\n';
    		}
    	}
    }
    

    时间复杂度:\(O(n)\)

    空间复杂度:\(O(1)\)

    C. Fish Eating

    在一个 \(m \times n\) 网格上,初始时全是障碍,没有鱼,在之后的若干次操作中会逐渐放置鱼。除了你操作的鱼可以动以外,别的鱼都是静止的。每条鱼有大小。当操作某条鱼时,要么将其移动到鱼已经被吃掉而留下的空白处,要么移动到与它相邻的(四个方向之一)、大小不大于它自己的鱼的位置并将其吃掉。每吃掉一条鱼,自己的大小会增加 \(1\)

    现在进行 \(q\) 次操作,每次会选择下面的某一种操作:

    • 给定 \(x,y,v\),表示移走 \((x, y)\) 处的障碍,并放置一条大小为 \(v\) 的鱼。保证该鱼的大小 \(v\) 不小于此前放的任何一条鱼。回答这条鱼现在最多吃多少条鱼(不会真的吃)。
    • 给定 \(x,y\)。设将 \((x, y)\) 处的鱼的大小增大任意值(不会真的增大),它吃掉的鱼的数量最大为 \(s\)(不会真的吃)。求出为使该鱼\(s\) 条鱼,其大小增大的值的最小值。

    保证 \(m \cdot n \leq 2.5 \times 10^5,\ q \leq 5 \times 10^5\)。强制在线处理。

    并查集是很容易想到的,因为需要涉及到连通块合并。第一种查询可以容易地用并查集维护。

    很容易注意到那个关键的性质:后来的鱼一定不小于已有的鱼。这意味着新加入的鱼的答案可以立刻确定,且对于某一条鱼,它的答案是随着加入的鱼变多不递减(不严格递增)的。比赛时想到这里就不知道怎么办了,仍然只能想出 \(O(q^2)\) 做法。

    还有一个关键的性质需要注意到:当两个连通块第一次被一条鱼连在一起时,来自一个连通块的鱼要想到达另一个,必须吃这一条“割边鱼”;即便后面加的鱼能使得这条鱼不再是割边,走这条鱼也不会更劣,因为后加的鱼一定不比他小。这启发我们考虑 Kruskal 重构树的思想(然而在比赛时我并不知道这个东西),维护一个带权并查集。

    每条鱼都是并查集的一个顶点。对于顶点 \(x\),设其父节点为 \(p_x\),其权值 \(ans_x\) 为“从这条鱼的位置出发,要吃完它所在连通块的所有鱼,初始时的大小的最小值”,当 \(x\) 为根节点时还要维护其树大小 \(s_x\)。在路径压缩时需要更新 \(v\) 的权值:

    \[\begin{array}{rl} 1 & \textbf{func $find(x)$:} \\ 2 & \qquad \textbf{if $p_x = x$: return $x$} \\ 3 & \qquad anc \gets find(p_x) \\ 4 & \qquad ans_x \gets \max\{ans_x, ans_{p_x}\} \\ 5 & \qquad p_x \gets anc \\ 6 & \qquad \textbf{return $anc$} \end{array} \]

    而当我们插入新的鱼时,要将与之相邻的连通块都以新鱼为根,并更新这些连通块的根的权值:

    \[\begin{array}{rl} 1 & \textbf{func $insertNewFish(i, x_0, y_0, v_i)$:} \quad \textrm{// 第 $i$ 条鱼在 $(x_0, y_0)$ 处,大小为 $v_i$} \\ 2 & \qquad p_i \gets i,\ s_i \gets 1,\ ans_i \gets 0 \\ 3 & \qquad \text{visit the adjacent grid $(x, y)$ in the 4 directions respectively:} \\ 4 & \qquad\qquad \textbf{if \textrm{$(x, y)$ does not have fish} : continue} \\ 5 & \qquad\qquad \textrm{Denote the fish at $(x, y)$ as $f$} \\ 6 & \qquad\qquad r \gets find(f) \\ 7 & \qquad\qquad p_r \gets i \\ 8 & \qquad\qquad s_i \gets s_i + s_r \\ 9 & \qquad\qquad ans_r \gets \max\{0, v_i - (s_r - 1)\} \end{array} \]

    对于第二种查询,只需输出 \(\max\{0, ans_i - v_i\}\) 即为答案,不过要先执行一次路径压缩以更新答案。

    size_t m, n, tc_num;
    std::vector<std::vector<size_t>> map;
    std::vector<uint32_t> vals, ans;
    std::vector<size_t> prts, szs;
    uint32_t last_ans;
    
    inline void preprocess() {
    	std::cin >> m >> n >> tc_num;
    	map.resize(m, std::vector<size_t>(n, -1));
    	auto tot = std::min(m * n, tc_num);
    	vals.reserve(tot);
    	ans.reserve(tot);
    	prts.reserve(tot);
    	szs.reserve(tot);
    }
    
    size_t find(size_t x) {
    	auto prt = prts[x];
    	if (prt == x) return x;
    	auto anc = find(prt);
    	ans[x] = std::max(ans[x], ans[prt]);
    	prts[x] = anc;
    	return anc;
    }
    
    inline void solve() {
    	int op;
    	uint32_t x, y;
    	std::cin >> op >> x >> y;
    	x ^= last_ans, y ^= last_ans;
    	--x, --y;
    
    	if (op == 1) {
    		assert(map[x][y] == size_t(-1));
    		auto id = vals.size();
    		map[x][y] = id;
    		prts.emplace_back(id);
    		szs.emplace_back(1);
    		vals.emplace_back();
    		std::cin >> vals[id];
    		ans.emplace_back(0);
    
    		constexpr std::array<std::pair<int, int>, 4>
    			DIRS{{{1, 0}, {0, 1}, {-1, 0}, {0, -1}}};
    		for (auto [dx, dy] : DIRS) {
    			ptrdiff_t cur_x = SC<ptrdiff_t>(x) + dx, cur_y = SC<ptrdiff_t>(y) + dy;
    
    			if (cur_x < 0 || cur_x >= SC<ptrdiff_t>(m) ||
    				cur_y < 0 || cur_y >= SC<ptrdiff_t>(n)) continue;
    
    			if (map[cur_x][cur_y] == size_t(-1)) continue;
    
    			auto rt = find(map[cur_x][cur_y]);
    			if (rt == id) continue;
    
    			prts[rt] = id;
    			szs[id] += szs[rt];
    			ans[rt] = std::max<int64_t>(0, SC<int64_t>(vals[id]) - (szs[rt] - 1));
    		}
    
    		last_ans = szs[id] - 1;
    	} else if (op == 2) {
    		auto id = map[x][y];
    		assert(~id);
    		find(id);
    		last_ans = std::max<int64_t>(0, SC<int64_t>(ans[id]) - vals[id]);
    	} else {
    		assert(false);
    	}
    	std::cout << last_ans << '\n';
    	std::cerr << last_ans << '\n';
    }
    
    inline int mainLoop() {
    	for (size_t tc = 0; tc < tc_num; ++tc) {
    		solve();
    	}
    	return 0;
    }
    

    时间复杂度:\(O(m \cdot n + q \log(m \cdot n))\)。如果使用哈希表存储地图,可以优化到 \(O(q \log(m \cdot n))\)

    空间复杂度:\(O(m \cdot n + \min\{m \cdot n, q\})\)。同样,如果用哈希表存地图,可以优化到 \(O(\min\{m\cdot n, q\})\)

    H. Rock-Paper-Scissors Master

    Alice 和 Bob 要玩 \(n \in \mathbb{N}\) 轮游戏。牌有石头、剪刀、布三种。两人初始时都各有三张牌,他们的牌始终都是公开的。在每一轮游戏中:

    1. Alice 选择一张牌打出。Bob 知道他打出了哪一张牌。
    2. 然后,Bob 也需要选择一张牌打出。
    3. 按照正常的石头剪刀布规则,比较两人的牌。如果 Alice 赢,他的得分加 \(3\);如果是平局,他的得分加 \(1\);如果 Alice 输了,他的得分不变。
    4. 已经打出的牌被舍弃。两个玩家各自独立地重新抽一张牌,每个玩家抽到每一种牌的概率都是 \(\dfrac{1}{3}\)

    Alice 需要最大化他的得分,而 Bob 需要最小化 Alice 的得分,他们都采取最优策略。

    给定初始牌组和 \(n\),求 Alice 在 \(n\) 轮后的期望分数。要求绝对误差和相对误差都不超过 \(10^{-6}\)

    保证 \(n \leq 10^9\)。一共有 \(T \leq 10^5\) 组测试点。

    Markov 决策过程是甚么?

    L. Substrings of Substrings

    2026 “钉耙编程”中国大学生算法设计暑期联赛(1)

    1006. 开关灯

    给定权值序列 \((a_i)_{i = 0}^{n - 1} \in \mathbb{N}^n\),表示有 \(n \in \mathbb{N}^*\) 盏灯,初始时都是熄灭的。在 \(\mathbb{N} \cap [0, n)\) 的所有排列中随机选一个记作 \((p_i)_{i = 0}^{n - 1}\),按该顺序依次点亮各灯。设第 \(i \in \mathbb{N} \cap [0, n)\) 步时,所有点亮的灯会形成 \(c_i\) 个极大连续段,则这一步的得分为 \(a_{p_i} \cdot c_i\)。求总得分的期望

    \[\mathbb{E}\left(\sum_{i = 0}^{n - 1}a_{p_i} \cdot c_i\right), \]

    答案对 \(M = 998244353\) 取模。

    保证 \(n \leq 2 \times 10^6\)\((\forall i \in \mathbb{N} \cap [0, n))\ (a_i < M)\)

    根据期望的线性性,我们可以将期望表达式转化为

    \[\sum_{i = 0}^{n - 1} a_i \cdot \mathbb{E}(c_{p^{-1}_i}), \]

    但我们并不知道这个期望怎么推。通过对 \(n \in [3, 10]\) 打表,我注意到(注意力难得惊人),枚举范围内的所有 \(n\) 都满足

    \[N(c_{p^{-1}_0}) = N(c_{p^{-1}_{n - 1}}) = \mathrm{P}_{n}^{n - 3} (n + 4), \\ (\forall i \in \mathbb{N} \cap [1, n - 2])\ (N(c_{p^{-1}_i}) = \mathrm{P}_{n}^{n - 3} (n + 3)), \]

    其中 \(N(\cdot)\) 表示古典概型计数。

    遂猜出答案。真他妈神了。

    下面还是严谨地推导一遍。

    j将这 \(n\) 个灯从左到右摆一排,则一个极大连续段有且仅有一个左端点。我们可以将连续段计数转换为左端点计数,而一个点是左端点,当且仅当以下条件都成立:

    • 该点的灯点亮。
    • 该点左侧没有灯或左侧的相邻灯熄灭。

    设事件 \(IL_{i, j}\) 表示“灯 \(i\) 被点亮时(即刚过时刻 \(p^{-1}_i\)时)灯 \(j\) 是某个极大连续段的左端点”。于是

    \[c_{p^{-1}_i} = \sum_{j = 0}^{n - 1} IL_{i, j}. \]

    利用期望的线性性得

    \[\mathbb{E}(c_{p^{-1}_i}) = \sum_{j = 0}^{n - 1} \mathbb{P}(IL_{i, j}). \]

    分类讨论 \(IL_{i, j}\)

    • \(j = 0\)\(j\) 左侧没有灯,它是左端点当且仅当它亮起。

      • \(i = 0\):显然 \(\mathbb{P}(IL_{0, 0}) = 1\)

      • \(i \neq 0\)\(\mathbb{P}(IL_{i, 0}) = \mathbb{P}(p^{-1}_i < p^{-1}_0)\)。因为 \(\vec{p}\) 是随机选取得排列,根据对称性和 \(i \neq 0\),有

        \[\mathbb{P}(p^{-1}_i < p^{-1}_0) = \mathbb{P}(p^{-1}_0 < p^{-1}_i) = \dfrac{1}{2}. \]

    • \(j \neq 0\)

    1005. 摩卡数

    \(\Sigma\) 是字母表(记其中第 \(i \in \mathbb{N} \cap [0, |\Sigma|)\) 个字母为 \(\sigma_i\))。对于字符串 \(s \in \Sigma^n\),记其前缀函数为 \(\pi\),其中

    \[\pi_i = \max\{j \in \mathbb{N} \cap [0, i]: s_{[0, j)} = s_{[i - j + 1, i]} \}\ (i \in \mathbb{N} \cap [0, n)). \]

    现在有算法

    \[\begin{array}{rl} 1 & \textbf{func $count(\Sigma, s)$:} \\ 2 & \qquad cnt \gets 0 \\ 3 & \qquad \textbf{for $i \in \mathbb{N} \cap [0, n - 2]$:} \\ 4 & \qquad\qquad \textbf{for $c \in \Sigma$:} \\ 5 & \qquad\qquad\qquad k \gets i \\ 6 & \qquad\qquad\qquad \textbf{while $k > 0 \land s_k \neq c$:} \\ 7 & \qquad\qquad\qquad\qquad cnt \gets cnt + 1 \\ 8 & \qquad\qquad\qquad\qquad k \gets \pi_{k - 1} \\ 9 & \qquad \textbf{return $cnt$} \end{array} \]

    给定 \(t \in \mathbb{N}^*\),需要选取合适的 \(\Sigma\)\(s \in \Sigma^n\ (n \in \mathbb{N}^*)\),使得 \(count(\Sigma, s) = t\)

    保证 \(t \leq 10^9\),要求 \(|\Sigma| \leq 26\) 且其中的字母从 \(\texttt{a}\) 开始按拉丁字母顺序取,要求 \(n \leq 10^5\)。有不超过 \(50\) 组测试数据。

    猜到了只需取 \(\Sigma = \{\texttt{a}, \texttt{b}\}\)。可惜没注意到在连续的 \(\texttt{a}\) 后面放连续的 \(\texttt{b}\) 是甚么效果。

    \(count\) 的返回值实际上就是从各个位置出发的错配次数之和,错配时会将指针 \(k\) 按照前缀函数往前跳。

    为了简便,先考虑只有一个字母的情况,即 \(\Sigma = \{\texttt{a}\}\)。此时因为都是同一个字母,所以不存在错配,即答案始终为 \(0\)

    那末,接下来考虑两个字母的情况,即 \(\Sigma = \{\texttt{a}, \texttt{b}\}\)。既然限制 \(|\Sigma| \leq 26\),我猜大概率是主要靠 \(s\) 做手脚,字母表取两个字母就够了(在计算理论中,两个字母也是最常用的字母表)。稍微考虑一下也容易发现,更多字母会导致计算答案时乘上因数,这会限制我们的发挥,对我们是很不利的。

    在这种字母表下考虑字符串 \(\texttt{a}^n\)。容易发现,此时每个位置都有 \(\pi_i = i + 1\),所以每次错配只会往前跳一步,从 \(i\) 出发会错配 \(i + 1\) 次,所以

    \[(\forall n \in \mathbb{N})\ \left(count(\Sigma, \texttt{a}^n) = \sum_{i = 0}^{n - 2} (i + 1) = \dfrac{1}{2} n (n - 1)\right). \]

    此后我考虑了能否通过交替两字母的连续段来构造,失败,因为第二次出现的 \(\texttt{a}\) 段会受第一次的影响,\(\texttt{b}\) 段同理。

    实际上,只需要构造 \(\texttt{a}^m\texttt{b}^n\) 就可以。对于 \(\texttt{a}\) 段,它的贡献由上面的公式可以得到,且不会受到 \(\texttt{b}\) 段影响;\(\texttt{b}\) 段因为前缀函数都是 \(0\)(因为字符串开头没有 \(\texttt{b}\)),所以每个 \(\texttt{b}\) 贡献都是 \(1\),而且同样也不会受到前面 \(\texttt{a}\) 的影响。因此,

    \[(\forall m \in \mathbb{N})\ (\forall n \in \mathbb{N})\ \left(count(\Sigma, \texttt{a}^m \texttt{b}^n = \dfrac{1}{2} m (m - 1) + n\right). \]

    \(m = \max\left\{x \in \mathbb{N}: \dfrac{1}{2} x (x - 1) \leq t\right\} = \left\lfloor\dfrac{1 + \sqrt{8 t + 1}}{2}\right\rfloor\)\(n = t - \dfrac{1}{2} m (m - 1)\) 即可,此时 \(n < m \leq \left\lfloor\dfrac{1 + \sqrt{8 \times 10^9 + 1}}{2}\right\rfloor = 44721 < 5 \times 10^4\),所以 \(m + n < 2m < 10^5\)

    inline uint32_t findMax(uint32_t target) {
    	constexpr double EPS = 1e-6;
    	return std::floor((std::sqrt(8 * SC<double>(target) + 1) + 1) / 2 + EPS);
    }
    
    inline void solve() {
    	uint64_t target;
    	std::cin >> target;
    	size_t m = findMax(target);
    	size_t n = target - ((SC<uint64_t>(m) * (m - 1)) >> 1);
    	std::cout << (m + n) << " 2\n";
    	for (size_t i = 0; i < m; ++i) std::cout << 'a';
    	for (size_t i = 0; i < n; ++i) std::cout << 'b';
    	std::cout << '\n';
    }
    

    时间复杂度:\(\Theta(\sqrt{t})\)

    空间复杂度:\(\Theta(1)\)

    2026 牛客暑期多校训练营 2

    最菜的一集。

    B. Bitwise Maximization

    给定 \((a_i) \in \mathbb{N}^n\ (n \in \mathbb{N}^*)\),记 \(N = \mathbb{N} \cap [0, n)\),求

    \[\max_{S \subseteq N} \left(\bigoplus_{i \in S} a_i + \bigoplus_{i \in N \setminus S} a_i\right). \]

    保证 \(n \leq 5 \times 10^5\)\((\forall i \in N)\ (a_i < 2^{30})\)

    三个人想了四小时也没能想出来。首 A 六分钟就过了。太绝望了。

    \(s = \bigoplus_{i \in S} a_i\)。我们考虑过将原式化为

    \[\bigoplus_{i \in S} a_i + \left(\bigoplus_{i \in S} a_i\right) \oplus s, \]

    但然后该怎么办就不知道了。ChatGPT 在这里使用了恒等式

    \[x + y = (x \oplus y) + 2(x \land y), \]

    于是原式化为

    \[\begin{align} & \left(\left(\bigoplus_{i \in S} a_i\right) \oplus \left(\bigoplus_{i \in S} a_i\right) \oplus s\right)+2\left(\left(\bigoplus_{i \in S} a_i\right) \land \left(\left(\bigoplus_{i \in S} a_i\right) \oplus s\right)\right) \\ = & s + 2\left(\left(\bigoplus_{i \in S} a_i\right) \land \neg s \right). \end{align} \]

    利用与对异或的分配律

    \[(x \oplus y) \land z = (x \land z) \oplus (y \land z) \]

    可进一步化为

    \[2 \bigoplus_{i \in S} (a_i \land \neg s) + s \]

    因为 \(s\) 已为定值,只需选取 \(S \subseteq N\) 最大化该式即可。

    题解从另一个角度考虑,代替了与对异或的分配律:\((s)_{(2)}\) 中的 \(1\) 在结果中必然为 \(1\),无需考虑;所以我们只需考虑 \((s)_{(2)}\) 中的为 \(0\) 位,即

    \[\left(\bigoplus_{i \in S} a_i\right) \land \neg s = \bigoplus_{i \in S} (a_i \land \neg s). \]

    最大子集异或和问题可以用异或线性基解决。我还给别人讲课讲过这个呢,看题解的时候还是没想到,真是废物,误人子弟。

    inline void solve() {
    	constexpr unsigned B = 30;
    	size_t n;
    	std::cin >> n;
    	std::vector<uint32_t> arr(n);
    	uint32_t sum = 0;
    	for (auto &e : arr) {
    		std::cin >> e;
    		sum ^= e;
    	}
    
    	for (auto &e : arr) e &= ~sum;
    
    	std::array<uint32_t, B> bases{};
    	for (auto e : arr) {
    		for (unsigned i = B; i--;) {
    			if ((e >> i) & 1) {
    				if (!bases[i]) {
    					bases[i] = e;
    					break;
    				}
    				e ^= bases[i];
    			}
    		}
    	}
    
    	uint32_t ans = 0;
    	for (unsigned i = B; i--;) {
    		ans = std::max(ans, ans ^ bases[i]);
    	}
    	ans = (ans << 1) + sum;
    	std::cout << ans << '\n';
    }
    

    时间复杂度:\(\Theta(Bn)\),其中 \(B = \log(\max \vec{a})\)

    空间复杂度:\(\Theta(n + B)\)

    G. GCD Graph

    给定 \(n \in \mathbb{N}^*\)。构造这样一张有向图 \(G = (V, E)\),其中 \(V = [n]\)

    \[E = \{(u, v, w) \in V \times V \times \mathbb{N}: u < v \land w = \gcd(u, v)\}. \]

    \(\delta(u, v)\ (u,v \in V,\ u < v)\) 表示从 \(u\)\(v\) 的最短路。给出 \(l, r \in [n]\ (l \leq r < n)\),求

    \[\sum_{i = l}^r \delta(i, n). \]

    保证 \(n \leq 10^7\)

    有不多于 \(100\) 组测试数据。

    我枚举几项后注意到

    \[\delta(u, v) = 1 + (u \not\perp v). \]

    遂想要通过线性筛分解质因数,然后用容斥数出不互质的数的个数。

    constexpr uint64_t N = 1e7;
    std::vector<bool> not_prime(N + 1);
    std::vector<uint64_t> primes;
    std::vector<uint64_t> mpfs(N + 1, UINT32_MAX);
    
    inline void preprocess() {
    	primes.emplace_back(2);
    	for (uint64_t i = 1; (i << 1) <= N; ++i) {
    		mpfs[i << 1] = 2;
    	}
    	for (uint64_t i = 3; i <= N; i += 2) {
    		if (!not_prime[i]) {
    			primes.emplace_back(i);
    			mpfs[i] = i;
    		}
    		for (auto j : primes) {
    			if (j * i > N) break;
    			not_prime[j * i] = true;
    			mpfs[j * i] = j; // j is the minimum prime factor of i * j
    			if (i % j == 0) break;
    		}
    	}
    }
    
    inline void solve() {
    	uint64_t l, r, n;
    	std::cin >> l >> r >> n;
    	std::vector<uint64_t> factors;
    	factors.reserve(7);
    	for (uint64_t i = n; i > 1;) {
    		auto mpf = mpfs[i];
    		factors.emplace_back(mpf);
    		while (i > 1 && i % mpf == 0) {
    			i /= mpf;
    		}
    	}
    
    	uint64_t ans = 0;
    	assert(factors.size() < 32);
    	for (uint64_t i = 1; i < (uint64_t(1) << factors.size()); ++i) {
    		uint64_t prod = 1;
    		for (unsigned j = 0; j < SC<unsigned>(factors.size()); ++j) {
    			if (i & (1u << j)) {
    				prod *= factors[j];
    			}
    		}
    		uint64_t floor = (l + prod - 1) / prod, ceil = r / prod, cnt = ceil - floor + 1;
    		if (std::popcount(i) & 1) {
    			ans += cnt;
    		} else {
    			ans -= cnt;
    		}
    	}
    	ans += r - l + 1;
    	std::cout << ans << '\n';
    }
    

    很神秘的是,如果我开一个 \(10^7\) 的数组而非 std::vector 的话,牛客会报“Compiler exceeded OUTPUT limit”,不知道甚么鬼。我换到牛客的编译器版本,测了也没有问题,用 Compiler Explorer 也没问题。

    很不幸,这次注意力不但没能帮我解题,反而浪费了大量时间在错误的思路上。这个式子在 \(2184\) 以下都没有反例,但从这个数字开始是有许多组的,在 \(10^5\) 范围内有:

    2184 2200
    27830 27846
    32214 32230
    57860 57876
    62244 62260
    87890 87906
    92274 92290
    
    posted @ 2026-07-22 23:04  我就是蓬蒿人  阅读(49)  评论(0)    收藏  举报