区间加区间异或和

省流:gemini 3.1 Pro 对这个经典难题提出了一个优于暴力的做法。 区间加区间异或和怎么做? 具体的,你需要维护一个整数数组,支持两种操作: 将一个连续区间内所有元素加上一个整数 查询一个连续区间内的元素的异或和 这个题有任何比较好的做法吗? 这是一个在算法竞赛(如 Codeforces、Luogu)中非常经典的高级数据结构难题。 区间加(Addition)和区间异或和(XOR Sum)在本质上是“不兼容”的。因为加法会产生进位(Carry),一个加法操作可能会同时改变多个连续的高位,这导致传统的线段树(Segment Tree)无法在 $O(\log n)$ 的时间内合并和下传 Lazy Tag。 目前解决这个问题最通用且高效的经典做法是:分块(Square Root Decomposition / Block Decomposition),结合按位拆分与二分查找。 以下是详细的做法和原理解析: 核心思想:按位拆分 + 余数定理 我们要查询区间异或和,实际上等价于:对于每一位 $k$($k \in [0, 30]$),统计区间内该位为 $1$ 的数字个数。如果个数为奇数,那么异或和的第 $k$ 位就是 $1$。 假设一个数字原来是 $x$,被加上了 $v$。那么 $x+v$ 的第 $k$ 位是否为 $1$,完全取决于它们对 $2^{k+1}$ 的余数。 具体规律如下: 设 $M = 2^{k+1}$, $H = 2^k$。 那么 $(x + v)$ 的第 $k$ 位为 $1$,当且仅当: $$ (x \bmod M + v \bmod M) \bmod M \ge H $$如果我们将 $y = x \bmod M$ 看作原数组的属性, $V = v \bmod M$ 看作懒标记(Lazy Tag),因为 $y \in [0, M-1]$ 且 $V \in [0, M-1]$,上述条件其实等价于 $y$ 落在了两个连续的区间内: ...

February 20, 2026

哈集幂!

原题链接 挺神妙的一个题吧。自己做的时候完全没做明白,看题解也看到晕晕乎乎的。今天重新从哈集幂的视角理解了一下,才发现确实挺妙(而且典)。 面对这个题首先做一些小转化。考虑一条边,它在哪几条路径上?我们用一个掩码($k$ 位二进制数)表示这个集合。第 $i$ 位为 1 代表这条边在 $a_i$ 与 $b_i$ 间的简单路径上。 我们考虑给树赋一个根,然后把每条边和它连接的两个点中深度较大的对应起来。一条路径经过这条边,当且仅当一个端点在这个点的子树中,而另一个端点不在子树中。这样,我们对每个点维护一个掩码,第 $i$ 位代表第 $i$ 条路径在该点子树中的端点的数目的奇偶性。不难发现这个掩码其实和这个点对应的边的掩码相等。 容易预处理这个掩码。具体的,考虑对第 $i$ 条路径的一个端点 $a$,$\text{mask}_a = \text{mask}_a \text{ xor } 2^i$。然后我们做一次 dfs,对每个点,先处理它的所有子节点,然后它的掩码异或上它所有子节点的掩码的异或和。 然后,我们就注意到,挑选一些边涂色,这时被满足(即有至少一条边被涂色)的简单路径的集合就是这些边对应的掩码(集合)的并集(或者按位或)。 至此,我们就完成了所有平凡的预处理工作。问题被转化为: 给定若干集合,求最少需要选取多少集合才能使得它们的并集为全集。 这个问题是本题的核心,也是一个非常经典的 NP-hard 问题。 我们考虑将最优化问题转化为判定问题:从小到大枚举需要多少集合,再判定这是否可行。 注意到答案最大等于全集大小。因为每个集合至少贡献一位,否则直接去除它更好。 进一步转化为计数问题:选出 $x$ 个集合,有多少种方案使得它们的并集为全集? 原题在这里要求选出的集合互不相同,但选出重复集合并不影响。 考虑哈集幂。我们构造一个长度为 $2^k$ 的序列 $a_i$(数组),其中第 $s$ 个位置是选出 $i$ 个集合,其并集是掩码为 $s$ 的集合的方案数。 显然有 $a_i = (a_1)^i$,这里的乘法是并集卷积,即 $(\text{or}, \times)$ 卷积。意思是,$a\times b$ 的第 $k$ 位是所有满足 $i \text{ or } j = k$ 的 $a_i \times b_j$ 的和。 ...

February 19, 2026

下载 ComfyUI 记录

我是 comfyUI 萌新。这篇文章技术干货不多,仅仅是下载和配置过程的记录。 省流:垃圾 最近听说了 Z-image turbo。非常感兴趣,于是打算部署一发。 comfyUI 是一款美观易用的本地部署应用。主要战场是扩散生图模型和视频生成,但同样支持 LLM。 主要特性是可以用图形化方式配置工作流(你可能想到了 scratch。没错,就是类似那样)。 我使用了它来运行 Z-image turbo。 下载非常简单。你需要访问官网,然后下载一个安装包。 有人可能用的是 portable。但我选择了 for Windows 的那个打包好的版本。 下载之后无脑跟随指示。 然后会自动下载和配置 pytorch,大概会下载几个 G(pytorch 本体 2.4GB 左右)。 可能会遇到神秘报错。忽略即可。只要能跑通就不是问题。 之后点击 comfyUI.exe 启动。过程也很简单。 可能会提示你选择一个目录放数据。这个目录建议选择空间大的盘符放,因为以后模型权重也要放在里面。 去官网下载 Z-image turbo 的权重。hugging face 不能直连,所以选择 modelscope。 实测这个仓库可用。 进入模型文件一栏,在 split_file 中,分别从 vae、diffusion model 和 text encoder 中挑一个你中意的下载下来。 注意模型的名字标明了量化的程度。text encoder 中,最大的那个无后缀的模型是全精度的。diffusion model 中,BF16 后缀的是全精度的。 量化精度高的模型性能更好。但是会更加消耗显存和算力,所以根据硬件资源量力而行。否则生成速度可能会非常缓慢,并且大量动用虚拟内存,甚至直接崩掉。 下载的模型权重放在你之前选择的那个目录(比如叫做 comfyUI-data)下的 \models\ 中的 \diffusion_models,\text_encoders 和 \vae 中。 注意绝对不是 \ComfyUI\resources\ComfyUI\models。如果你忘记了之前选了哪个目录,你可以进入 comfyUI,点击图标打开菜单,然后选择帮助一栏下的“open folder”,“open model folder”(英文)或者“打开模型文件夹”(中文)。 ...

January 31, 2026

无题

醒醒,星星。

January 27, 2026

趁着年轻,好好犯病

演得像真的一样,去拯救世界吧。 毕竟,意义本身没有意义。 ——而意义本身,就是意义。

January 27, 2026

小学奥数合集

永远在增加新题目的路上。 一根长度为 1,宽度不计的针,抽象为线段。在平面上,你需要通过一系列平移和旋转操作使得针扫过每个方向,并最后回到原位,请问针经过的所有点的集合的面积最小是多少? 给定六个正整数,你需要分别以前三个和后三个为边长构造同一平面上的两个三角形并最大化它们的交集的面积,请问这个面积是多少? 请使用实数常量,不等关系与等于号,加法和乘法,逻辑连接词与实数上的量词,写出一个公式 $\Phi$,使得对任意实数 $x$,它是整数当且仅当 $\Phi(x)$ 成立 一根长 1 公里的橡皮筋。一只蚂蚁在它的一端,以每秒 1 厘米的速度向另一端爬行。与此同时,橡皮筋自身以每秒 1 公里的速度均匀伸长(比如,它的中点以 0.5 公里/秒的速度远离起点,终点以 1 公里/秒的速度远离起点)。请问,蚂蚁最终能爬到终点吗? 在平面上给定一条任意的简单封闭曲线(即首尾相连、不自交的环路),请问是否一定能在曲线上找到四个点,使得它们构成一个正方形的四个顶点? 有一块 $m \times n$ 的矩形巧克力,左上角那一格是苦的(毒药), 两个人轮流掰巧克力。 每次必须选剩下的巧克力中的某一格,把这一格右边和下边的所有部分(包括这一格自己)全部掰掉吃掉。 吃到左上角苦巧克力的人输。 请问:对于任意 $m, n > 1$,先手必胜还是后手必胜? 如果一方必胜,第一步该怎么掰? 在宽度为 1 的 L 型走廊直角转弯处,能够通过的刚性(不可形变)二维物体的最大面积是多少? 我们把一个自然数 $n$ 写成“遗传 $k$ 进制”的形式。比如在 2 进制下,$19 = 2^4 + 2^1 + 2^0$,我们把指数也必须写成 2 进制,直到所有数字只包含 $k$ 和 0。 比如 $19$ 在 2 进制的遗传写法是 $2^{2^2} + 2^1 + 2^0$。 ...

January 24, 2026

Exchange Method

你的调整法证明可能假了! 看到 P1012 被升蓝,以及 Register_int 的帖子,感慨良多。 事实上,我发现自己之前许多调整法/交换论证的证明是假的。所以写这篇文章来整理交换论证相关。 邻项交换 若对于相邻两项 $x, y$,让 $x$ 在 $y$ 前面更优,我们称 $x < y$。 很多证明可能到这一步就停止了。然而这是错误的。仅仅具备这样的性质,并不能保证“对于任意相邻两项 $x, y$,其中靠前的 $x$ 满足 $x < y$”的序列是唯一的。 简单的反例:$x < y, y < z, z < x$。这时 $(x, y, z)$ 与 $(z, x, y)$ 均满足条件。 不同的序关系 oi-wiki 有所提及:https://oi-wiki.org/math/order-theory/ 重点在于定义的那些性质,以及全序、偏序、严格偏序、弱序、严格弱序等不同种类的序关系。 众所周知,如果把 $\lambda x . \lambda y . \; (x\leq y)$ 放进 std::sort 中,很有可能会得到死循环的结果。因为它要求严格弱序,即需要非自反性和非对称性。简单来说,$x < y \text{ and } y < x$ 与 $x < x$ 两个性质都是恒不成立的。同时,严格弱序还要求不可比性是一个等价关系(即满足自反性、传递性和对称性,如果不可比性不是等价关系,则这只是严格偏序而不是严格弱序)。 ...

January 16, 2026

解题过程实录 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

Regular Engine

亲手实现一个正则引擎! 众所周知,一类经典的自动机是有限状态自动机。它们识别的语言是正则语言,而正则表达式能够匹配的字符串的集合也是正则语言。 (如果你之前没有了解过,询问 AI 或是使用搜索引擎都很适合快速了解) 导数法 要匹配一个正则表达式,最经典的方法就是构造对应的 NFA,然后通过一些手法转化为 DFA。但这太复杂了。有没有更简单的方法呢? 有一种被称为“导数法”的方法。 思想非常简单:我们将构造出的 DFA 编码为一个正则。这样,转移之后相当于产生了一个新的自动机,也就是一个新的正则表达式。 原始正则表达式是 $r$。那么,$r$“吃掉”一个字符 $c$ 之后产生的表达式 $r'$,能匹配的所有字符串就是所有满足 (match? (cat c s) r) 的字符串 $s$。 读者可以自行思考一下导数公式。 我想用代码表示是最清晰的。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 (define (derive re c) (match re [(Void) (Void)] [(Eps) (Void)] [(Dot) (Eps)] [(Chr ch) (if (char=? ch c) (Eps) (Void))] [(Alt e1 e2) (make-alt (derive e1 c) (derive e2 c))] [(Cat e1 e2) (if (nullable? e1) (make-alt (make-cat (derive e1 c) e2) (derive e2 c)) (make-cat (derive e1 c) e2))] [(Star e) (make-cat (derive e c) (Star e))])) 导数法将是我们使用的核心算法。 特性与抽象语法 我们将实现支持连接(cat),选择(alt)与闭包(kleene star)三种特性的正则表达式。同时添加“任意字符”(Dot)这种有用的语法糖(为了辅助包含匹配)。 ...

January 3, 2026