解题过程实录 ARC117D

原题链接 首先看上去像差分约束,但边太多不太对。 假如一个点标 $x$,距离它 $d$ 的点可能标 $x-d$(如果 $x>d$),否则只能标 $x+d$。 这样是不是只需要考虑一个点对应的约束就做完了?但事实上(根据三角形不等式)其他约束几乎从来不会被自动满足(除非是链)。 绝对值。前两天做的题中就有绝对值。绝对值对应到距离。那么设法把树拍平到数轴上。 按照 DFS 序给树上节点标号?不太对。欧拉序?对完了。考虑将每个点的标号设为它在欧拉序中第一次出现的位置。 随手写一发哦哦过不了样例啊。有点麻烦,欧拉序一定是合法的,但未必是最优的。 我需要枚举每个点作为根吗?复杂度太高。怎么办? 注意到根的标号一定是 1。那么,考虑标号最大的节点,它是在欧拉序中最后出现的节点。我们要尽可能把它往前排。 也就是说,最小化它前面的“冗余”(那些在退出一个点时产生的记录)的节点数。 冗余的节点数……注意到我们可以挑一条(起点为根的)路径不产生冗余节点数,这时我们遍历的最后一个点是这条路径的终点。而其他地方必然产生冗余记录。 那么挑选直径作为这条路径。大胆写一发,发现 AC 了。 不过为什么这是对的?首先我们的构造能够达到 $2n-l$,其中 $l$ 是树的直径的长度(点数)。 能不能更优?答案是否定的。如何证明呢? 考虑将所有点按标号大小升序排序。我们假想进行一次遍历:从标号最小的点开始,每次沿两点间唯一简单路径走到下一个点,直到标号最大的点结束。 我们记 $d_i$ 是遍历到第 $i$ 个(意味着标号第 $i$ 大)点时已经走过的距离(边数),$p_i$ 是第 $i$ 个点,$a_i$ 是第 $i$ 个点的标号。 注意到 $d_{i+1} - d_i = \operatorname{dis}(p_{i+1}, p_i) \leq a_{i+1}-a_i$,即标号之差不小于距离。 注意到让第 $i$ 个点标号为 $d_i+1$(加一是为了让所有点标号不小于 1)时,每个不等号都可以取等。由于 $a_1$ 不变,取等显然更优。 并且取等的情况同样都可以建模为一种遍历顺序。 那么考虑最优化遍历顺序。 优化目标是 $d_n$,即总共经过的边的数目。 使用一种拆贡献的方法:考虑每条边被经过了多少次。 每条边被经过两次是显然上界。 那么考虑让尽可能多的边只被遍历一次。 注意到遍历一次就像“过桥”,如果从这条边砍断后的两个联通分量中的一个到了另一个,就无法再回去了。 显然所有遍历一次的边形成了一条链(这可以用归纳法证明)。 最大化它们的数目就是最大化链的长度。 不妨取直径作为这条链。 注意到只遍历一次的边的数目不可能更多,即不会因为有边走了超过两次而增加。 所以剩下的边一定遍历两次。 ...

January 10, 2026

解题过程实录 P10283

备注:解题过程实录不是题解。它是做题过程中的思维草稿,可能比较语无伦次。 并且由于中途有中断(比如去上学),可能会有一些内容看上去“奇迹般地突然出现”。 原题链接 这是个什么玩意儿。即没有任何奶牛是另一个奶牛的前缀。 首先前缀具备偏序性质。是不是该考虑拓扑排序? 不过挂上 01 Trie 更好。这样获得一个简洁的模型:每个点走到它子树中的一个位置去,最终没有任何点的子树中有别的东西。 最小化走动的距离的和。 对于一个节点对应的子树,记“边缘节点集合”为: 如果这个节点上有奶牛(即有奶牛的 01 串是这个节点对应的 01 串),且子树中没有奶牛:包含它自己的集合。 如果这个节点的子树中是空的(即没有任何奶牛的 01 串在这个节点的子树中):包含它自己的集合,但是有标记表明它是另一类节点。 其他情况:两个子树的边缘节点集合的并集。 (将 1、2 两个 case 合称原子子树) 将两类节点分开维护并按深度排序,从两类中的最浅点中抉择。这里可以考虑优先队列启发式合并。 之后,整个过程就转化为递归的类似树形 dp 的不断合并。 对于一个非原子子树(即除根节点外的其他点中有奶牛),考虑将根节点插入到子树中的某个边缘节点上。 注意被新节点插入之后空节点会转化为满节点。而满节点会转化为它的两个子节点。 满节点的代价是深度 +2,空节点的代价是深度。 一个重要的问题是,一个点上可能有多个奶牛。这也比较好解决:多次插入同样的节点即可。 而对于原子子树上的多个奶牛,可以先构造一个只有一个奶牛的原子子树,然后将剩余的节点变成插入过程。 一般来说,ad-hoc 题想到核心 idea 就过了。然而这个题考验精细实现和卡常。 如果你不使用指针,可能会多出许多不必要的拷贝,这样就会 TLE。换用 pbds 配对堆会 T 的更惨,也许是因为指针结构 copy 和 insert 的常数更大。 代码: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 #include <bits/stdc++.h> using namespace std; struct node { int cnt; node *left, *right; node() = default; } *rt = nullptr; void trie_insert(node *&p, const basic_string<char> &s, const int depth) { if (!p) { p = new node(); } if (depth == s.size()) { p->cnt++; } else { if (s[depth] == '0') { trie_insert(p->left, s, depth + 1); } else { trie_insert(p->right, s, depth + 1); } } } using set_t = priority_queue<int, vector<int>, greater<>>; int64_t ans = 0; void ins(set_t &f, set_t &e, int c, const int depth) { while (c) { if (f.empty()) { ans += e.top() - depth; f.push(e.top()); e.pop(); } else if (e.empty()) { const int x = f.top(); f.pop(); ans += x - depth + 2; f.push(x + 1); f.push(x + 1); } else { if (e.top() < f.top() + 2) { ans += e.top() - depth; f.push(e.top()); e.pop(); } else { const int x = f.top(); f.pop(); ans += x - depth + 2; f.push(x + 1); f.push(x + 1); } } c--; } } pair<set_t *, set_t *> query(const node *const p, const int depth) { // 前满后空 if (!p) { auto *q = new set_t(); q->push(depth); return {new set_t(), q}; } else { if (!p->left && !p->right) { auto *q = new set_t(), *tmp = new set_t(); q->push(depth); ins(*q, *tmp, p->cnt - 1, depth); return {q, tmp}; } else { auto [x,y] = query(p->left, depth + 1); auto [u,v] = query(p->right, depth + 1); if (x->size() < u->size()) { swap(x, u); } while (!u->empty()) { x->push(u->top()); u->pop(); } if (y->size() < v->size()) { swap(y, v); } while (!v->empty()) { y->push(v->top()); v->pop(); } ins(*x, *y, p->cnt, depth); return {x, y}; } } } int n; int main() { ios_base::sync_with_stdio(false); cin.tie(nullptr); cin >> n; for (int i = 0; i < n; ++i) { basic_string<char> s; cin >> s; trie_insert(rt, s, 0); } query(rt, 0); cout << ans; return 0; }

January 10, 2026

Solution of ABC438G

题目链接 题外话: 数竞党的防御性证明会用严谨的逻辑说明结论的正确性,而对“解题思路是如何想到的”一类的问题没有任何启发;oier/acmer 的题解则大多会提示我们从哪里入手,却不加严谨的证明。而本题解采取折中的方案:既对解题思路没有任何启发,也不加严谨的证明。 考虑 gcd 的动机在于,对于这样的题,$k$ 巨大。 于是考虑循环节。发现循环节是 $\mathrm{lcm}(n, m)$。 这时,我们考虑完整的循环节。 发现在 $n$ 与 $m$ 不互素时这是麻烦的,而 $n$ 与 $m$ 互素时一个循环节内每对 $(a_i, b_j)$ 刚好出现且仅出现一次。 这时考虑 $g = \gcd(n, m)$。 发现,对于任意 $r$,一个模 $g$ 余 $r$ 的 $i$ 只会和模 $g$ 余 $r$ 的 $j$ 配对。 这样就能够把非互素情形转化为互素情形。 对于互素情形,循环节是容易处理的。 考虑多余的一部分。这时,考虑枚举 $i$,寻找能与 $i$ 配对的 $j$。 记 $n' = n / g, m' = m / g$。 这时,对于一个下标 $i$,能够与它配对的任意 $j$ 与 $kn'+i, k \in \N$ 在模 $m'$ 意义下同余。 ...

December 28, 2025