<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="zh_CN">
  <title>我的博客</title>
  <subtitle>个人博客与技术笔记</subtitle>
  <link href="https://zxhyczx.github.io/" rel="alternate" type="text/html"/>
  <link href="https://zxhyczx.github.io/atom.xml" rel="self" type="application/atom+xml"/>
  <id>https://zxhyczx.github.io/</id>
  <updated>2026-10-10T00:00:00.000Z</updated>
  <entry>
    <title>11.10 比赛全套题解 (CodeForces A ~ M)</title>
    <link href="https://zxhyczx.github.io/blog/posts/contest-11-10-solutions/" rel="alternate" type="text/html"/>
    <id>https://zxhyczx.github.io/blog/posts/contest-11-10-solutions/</id>
    <published>2026-10-10T00:00:00.000Z</published>
    <updated>2026-10-10T00:00:00.000Z</updated>
    <summary>11.10 模拟赛全套题解汇总，涵盖 CodeForces A ~ M 题目的核心考点、算法思路与完整参考代码。</summary>
    <content type="html"><![CDATA[<h2>目录与题目汇总</h2>
<table>
<thead>
<tr>
<th>题号</th>
<th>题目名称</th>
<th>核心考点与算法</th>
<th>CodeForces 原题链接</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>A</strong></td>
<td>Water Buying</td>
<td>贪心、数学</td>
<td><a href="https://codeforces.com/problemset/problem/1118/A">CF 1118A</a></td>
</tr>
<tr>
<td><strong>B</strong></td>
<td>Edge Deletion</td>
<td>图论、Dijkstra 最短路树</td>
<td><a href="https://codeforces.com/problemset/problem/1076/D">CF 1076D</a></td>
</tr>
<tr>
<td><strong>C</strong></td>
<td>Basketball Exercise</td>
<td>线性 DP</td>
<td><a href="https://codeforces.com/problemset/problem/1195/C">CF 1195C</a></td>
</tr>
<tr>
<td><strong>D</strong></td>
<td>Tree Painting</td>
<td>换根 DP</td>
<td><a href="https://codeforces.com/problemset/problem/1187/E">CF 1187E</a></td>
</tr>
<tr>
<td><strong>E</strong></td>
<td>LCM</td>
<td>数论（因数分解）</td>
<td><a href="https://codeforces.com/problemset/problem/1068/B">CF 1068B</a></td>
</tr>
<tr>
<td><strong>F</strong></td>
<td>Easy Problem</td>
<td>状态机线性 DP</td>
<td><a href="https://codeforces.com/problemset/problem/1096/D">CF 1096D</a></td>
</tr>
<tr>
<td><strong>G</strong></td>
<td>Catch Overflow!</td>
<td>栈模拟循环</td>
<td><a href="https://codeforces.com/problemset/problem/1175/B">CF 1175B</a></td>
</tr>
<tr>
<td><strong>H</strong></td>
<td>Magic Ship</td>
<td>二分答案 + 曼哈顿距离</td>
<td><a href="https://codeforces.com/problemset/problem/1117/C">CF 1117C</a></td>
</tr>
<tr>
<td><strong>I</strong></td>
<td>Maximal Continuous Rest</td>
<td>环形数组断环成链</td>
<td><a href="https://codeforces.com/problemset/problem/1141/B">CF 1141B</a></td>
</tr>
<tr>
<td><strong>J</strong></td>
<td>Cow and Snacks</td>
<td>图论生成树、并查集</td>
<td><a href="https://codeforces.com/problemset/problem/1209/D">CF 1209D</a></td>
</tr>
<tr>
<td><strong>K</strong></td>
<td>Array and Segments (Hard version)</td>
<td>线段树扫描线</td>
<td><a href="https://codeforces.com/problemset/problem/1108/E2">CF 1108E2</a></td>
</tr>
<tr>
<td><strong>L</strong></td>
<td>Vasya and Triangle</td>
<td>数论与几何构造</td>
<td><a href="https://codeforces.com/problemset/problem/1030/D">CF 1030D</a></td>
</tr>
<tr>
<td><strong>M</strong></td>
<td>Two Arrays and Sum of Functions</td>
<td>贡献法 + 排序不等式</td>
<td><a href="https://codeforces.com/problemset/problem/1165/E">CF 1165E</a></td>
</tr>
</tbody>
</table>
<hr />
<h2>A. Water Buying (<a href="https://codeforces.com/problemset/problem/1118/A">CodeForces 1118A</a>)</h2>
<ul>
<li><strong>标签</strong>：贪心、数学</li>
</ul>
<h3>题目大意</h3>
<p>需要购买 $n$ 升水。1 升装售价 $a$，2 升装售价 $b$。求最少需要花费多少钱。</p>
<h3>算法思路</h3>
<p>比较两种购买方式的单价性价比：</p>
<ul>
<li>买两瓶 1 升装需要花费 $2a$。</li>
<li>若 $2a \le b$，说明 1 升装更划算或价格相同，全部购买 1 升装即可，花费为 $n \times a$。</li>
<li>若 $2a &gt; b$，说明 2 升装更划算。应尽可能多买 2 升装，即购买 $\lfloor n / 2 \rfloor$ 瓶 2 升装；若 $n$ 为奇数，剩下的 1 升补买 1 瓶 1 升装，总花费为：
$$\lfloor n / 2 \rfloor \times b + (n \bmod 2) \times a$$</li>
</ul>
<p>综合两式，直接取最小值即可：
$$\min\Big(n \times a,; \lfloor n / 2 \rfloor \times b + (n \bmod 2) \times a\Big)$$</p>
<h3>复杂度分析</h3>
<ul>
<li><strong>时间复杂度</strong>：$O(1)$。</li>
<li><strong>空间复杂度</strong>：$O(1)$。</li>
</ul>
<h3>参考代码</h3>
<pre><code>#include &lt;bits/stdc++.h&gt;
using namespace std;
#define int long long

void solve() {
    int n, a, b;
    cin &gt;&gt; n &gt;&gt; a &gt;&gt; b;
    int x = n * a;
    int y = (n % 2) * a + (n / 2) * b;
    cout &lt;&lt; min(x, y) &lt;&lt; "\n";
}

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int t;
    cin &gt;&gt; t;
    while (t--) {
        solve();
    }
    return 0;
}
</code></pre>
<hr />
<h2>B. Edge Deletion (<a href="https://codeforces.com/problemset/problem/1076/D">CodeForces 1076D</a>)</h2>
<ul>
<li><strong>标签</strong>：图论、Dijkstra、最短路树 (SPT)、贪心</li>
</ul>
<h3>题目大意</h3>
<p>给定包含 $n$ 个点、$m$ 条边的带正权连通无向图。现在需要保留至多 $k$ 条边。
定义“好点”：如果某个节点在保留边构成的新图中的最短路（以 1 为源点）与原图完全相等，则称该点为好点。
要求最大化好点的数量，输出保留的边数及保留边的编号。</p>
<h3>算法思路</h3>
<ol>
<li>从源点 1 出发的所有单源最短路径构成一棵最短路树（Shortest Path Tree, SPT），最多包含 $n-1$ 条边。</li>
<li>观察性质：只要一个点 $v$ 到根节点 1 的路径上的所有树边都被保留，$v$ 在新图中的最短距离就一定与原图一致，因此 $v$ 成为好点。</li>
<li>任何一条保留的边最多只能将 1 个新的好点连接到以 1 为根的连通分量中。因此保留 $k$ 条边最多产生 $1 + \min(k, n-1)$ 个好点。</li>
<li>我们在运行 Dijkstra 算法时，当一个点 $u$ 被从优先队列中取出（距离确定）时，记录将其松弛的树边 <code>parent_edge[u]</code>。</li>
<li>按照节点被确定的先后顺序（即距离递增的拓扑/BFS顺序），依次选取前 $\min(k, n-1)$ 条树边。因为边权为正，后确定的节点的祖先必已确定，这样保证了被选取的边始终构成包含根节点 1 的一棵连通子树。</li>
</ol>
<h3>复杂度分析</h3>
<ul>
<li><strong>时间复杂度</strong>：$O(m \log n)$。</li>
<li><strong>空间复杂度</strong>：$O(n + m)$。</li>
</ul>
<h3>参考代码</h3>
<pre><code>#include &lt;bits/stdc++.h&gt;
using namespace std;

struct Edge {
    int to;
    long long w;
    int id;
};

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n, m, k;
    if (!(cin &gt;&gt; n &gt;&gt; m &gt;&gt; k)) return 0;

    vector&lt;vector&lt;Edge&gt;&gt; adj(n + 1);
    for (int i = 1; i &lt;= m; i++) {
        int u, v;
        long long w;
        cin &gt;&gt; u &gt;&gt; v &gt;&gt; w;
        adj[u].push_back({v, w, i});
        adj[v].push_back({u, w, i});
    }

    const long long INF = 1e18;
    vector&lt;long long&gt; dist(n + 1, INF);
    vector&lt;int&gt; parent_edge(n + 1, 0);
    vector&lt;bool&gt; vis(n + 1, false);

    priority_queue&lt;pair&lt;long long, int&gt;, vector&lt;pair&lt;long long, int&gt;&gt;, greater&lt;pair&lt;long long, int&gt;&gt;&gt; pq;
    dist[1] = 0;
    pq.push({0, 1});

    vector&lt;int&gt; ans;

    while (!pq.empty()) {
        auto [d, u] = pq.top();
        pq.pop();

        if (vis[u]) continue;
        vis[u] = true;

        if (u != 1 &amp;&amp; (int)ans.size() &lt; k) {
            ans.push_back(parent_edge[u]);
        }

        for (const auto&amp; edge : adj[u]) {
            int v = edge.to;
            if (d + edge.w &lt; dist[v]) {
                dist[v] = d + edge.w;
                parent_edge[v] = edge.id;
                pq.push({dist[v], v});
            }
        }
    }

    cout &lt;&lt; ans.size() &lt;&lt; "\n";
    for (int i = 0; i &lt; (int)ans.size(); i++) {
        cout &lt;&lt; ans[i] &lt;&lt; (i + 1 == (int)ans.size() ? "" : " ");
    }
    cout &lt;&lt; "\n";

    return 0;
}
</code></pre>
<hr />
<h2>C. Basketball Exercise (<a href="https://codeforces.com/problemset/problem/1195/C">CodeForces 1195C</a>)</h2>
<ul>
<li><strong>标签</strong>：动态规划、线性 DP</li>
</ul>
<h3>题目大意</h3>
<p>两排学生，每排各 $n$ 人。第 1 排学生身高为 $h_{1,i}$，第 2 排学生身高为 $h_{2,i}$。
要求挑选若干学生组成篮球队，选出的学生列号必须严格单调递增，且队伍所在的行必须严格交替。求选出学生的身高总和最大值。</p>
<h3>算法思路</h3>
<p>定义状态：</p>
<ul>
<li>$f[i][0]$：在前 $i$ 列中选人，且第 $i$ 列选第 1 排的最大身高总和；</li>
<li>$f[i][1]$：在前 $i$ 列中选人，且第 $i$ 列选第 2 排的最大身高总和。</li>
</ul>
<p>转移方程：
$$f[i][0] = \max(f[i-1][1], f[i-2][1]) + h_{1, i}$$
$$f[i][1] = \max(f[i-1][0], f[i-2][0]) + h_{2, i}$$</p>
<p>最终答案为 $\max(f[n][0], f[n][1], f[n-1][0], f[n-1][1])$。</p>
<h3>复杂度分析</h3>
<ul>
<li><strong>时间复杂度</strong>：$O(n)$。</li>
<li><strong>空间复杂度</strong>：$O(n)$。</li>
</ul>
<h3>参考代码</h3>
<pre><code>#include &lt;bits/stdc++.h&gt;
#define int long long
using namespace std;

int f[100005][2], a[100005][2];

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n;
    if (!(cin &gt;&gt; n)) return 0;

    for (int i = 1; i &lt;= n; i++) cin &gt;&gt; a[i][0];
    for (int i = 1; i &lt;= n; i++) cin &gt;&gt; a[i][1];

    f[1][0] = a[1][0];
    f[1][1] = a[1][1];

    for (int i = 2; i &lt;= n; i++) {
        for (int j = 0; j &lt;= 1; j++) {
            f[i][j] = max(f[i - 1][1 - j], f[i - 2][1 - j]) + a[i][j];
        }
    }

    cout &lt;&lt; max({f[n - 1][0], f[n - 1][1], f[n][0], f[n][1]}) &lt;&lt; "\n";
    return 0;
}
</code></pre>
<hr />
<h2>D. Tree Painting (<a href="https://codeforces.com/problemset/problem/1187/E">CodeForces 1187E</a>)</h2>
<ul>
<li><strong>标签</strong>：树形 DP、换根 DP</li>
</ul>
<h3>题目大意</h3>
<p>给定包含 $n$ 个节点的无向树，初始所有点均为白色。</p>
<ul>
<li>第 1 步：任选一个白点染黑，得分加上该点所在的白色连通块大小（初始为 $n$）。</li>
<li>第 $i$ 步（$2 \le i \le n$）：选取一个与已有黑点相邻的白点染黑，得分加上该点染色前所在白色连通块的大小。
求将全部节点染黑所能获得的最大总得分。</li>
</ul>
<h3>算法思路</h3>
<ol>
<li>当以 $r$ 为初始根节点时，总得分为所有节点以 $r$ 为根时的子树大小之和：
$$dp[r] = \sum_{u=1}^n \text{size}_r(u)$$</li>
<li>换根 DP 转移：
<ul>
<li>第一次 DFS：选 1 为根，统计子树大小 $sz[u]$ 以及 $dp[1] = \sum sz[u]$。</li>
<li>第二次 DFS：根由 $u$ 移动到子节点 $v$：
$$dp[v] = dp[u] - sz[v] + (n - sz[v]) = dp[u] + n - 2 \cdot sz[v]$$</li>
</ul>
</li>
<li>遍历所有节点求 $\max dp[u]$。</li>
</ol>
<h3>复杂度分析</h3>
<ul>
<li><strong>时间复杂度</strong>：$O(n)$。</li>
<li><strong>空间复杂度</strong>：$O(n)$。</li>
</ul>
<h3>参考代码</h3>
<pre><code>#include &lt;bits/stdc++.h&gt;
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n;
    if (!(cin &gt;&gt; n)) return 0;

    vector&lt;vector&lt;int&gt;&gt; adj(n + 1);
    for (int i = 0; i &lt; n - 1; i++) {
        int u, v;
        cin &gt;&gt; u &gt;&gt; v;
        adj[u].push_back(v);
        adj[v].push_back(u);
    }

    vector&lt;long long&gt; sz(n + 1, 0);
    vector&lt;long long&gt; dp(n + 1, 0);

    auto dfs1 = [&amp;](auto&amp; self, int u, int p) -&gt; void {
        sz[u] = 1;
        for (int v : adj[u]) {
            if (v == p) continue;
            self(self, v, u);
            sz[u] += sz[v];
        }
        dp[1] += sz[u];
    };

    dfs1(dfs1, 1, 0);

    long long max_ans = dp[1];

    auto dfs2 = [&amp;](auto&amp; self, int u, int p) -&gt; void {
        for (int v : adj[u]) {
            if (v == p) continue;
            dp[v] = dp[u] - sz[v] + (n - sz[v]);
            max_ans = max(max_ans, dp[v]);
            self(self, v, u);
        }
    };

    dfs2(dfs2, 1, 0);

    cout &lt;&lt; max_ans &lt;&lt; "\n";
    return 0;
}
</code></pre>
<hr />
<h2>E. LCM (<a href="https://codeforces.com/problemset/problem/1068/B">CodeForces 1068B</a>)</h2>
<ul>
<li><strong>标签</strong>：数论、因数分解</li>
</ul>
<h3>题目大意</h3>
<p>给定正整数 $b$（$b \le 10^{10}$），对于所有正整数 $a$，求 $\frac{\text{LCM}(a, b)}{a}$ 的不同取值个数。</p>
<h3>算法思路</h3>
<p>由公式：
$$\frac{\text{LCM}(a, b)}{a} = \frac{b}{\gcd(a, b)}$$
由于 $\gcd(a, b)$ 可以取遍 $b$ 的每一个正因数，因此不同取值的个数严格等于 $b$ 的因数个数 $d(b)$。在 $O(\sqrt{b})$ 内枚举因数即可。</p>
<h3>复杂度分析</h3>
<ul>
<li><strong>时间复杂度</strong>：$O(\sqrt{b})$。</li>
<li><strong>空间复杂度</strong>：$O(1)$。</li>
</ul>
<h3>参考代码</h3>
<pre><code>#include &lt;bits/stdc++.h&gt;
#define int long long
using namespace std;

signed main() {
    int b;
    if (cin &gt;&gt; b) {
        int ans = 0;
        for (int i = 1; i * i &lt;= b; ++i) {
            if (b % i == 0) {
                ans += (i * i == b ? 1 : 2);
            }
        }
        cout &lt;&lt; ans &lt;&lt; "\n";
    }
    return 0;
}
</code></pre>
<hr />
<h2>F. Easy Problem (<a href="https://codeforces.com/problemset/problem/1096/D">CodeForces 1096D</a>)</h2>
<ul>
<li><strong>标签</strong>：动态规划、字符串</li>
</ul>
<h3>题目大意</h3>
<p>给定长度为 $n$ 的字符串 $s$。删除第 $i$ 个字符的花费为 $a_i$。求最小的花费，使得剩余的字符串中不包含子序列 <code>"hard"</code>。</p>
<h3>算法思路</h3>
<p>维护前缀串消除各个前缀子序列的代价：</p>
<ul>
<li>$dp[0]$：无子序列 <code>"h"</code>；</li>
<li>$dp[1]$：无子序列 <code>"ha"</code>；</li>
<li>$dp[2]$：无子序列 <code>"har"</code>；</li>
<li>$dp[3]$：无子序列 <code>"hard"</code>。</li>
</ul>
<p>转移：</p>
<ul>
<li>$s[i] == \text{'h'}$：$dp[0] \gets dp[0] + a_i$；</li>
<li>$s[i] == \text{'a'}$：$dp[1] \gets \min(dp[1] + a_i, dp[0])$；</li>
<li>$s[i] == \text{'r'}$：$dp[2] \gets \min(dp[2] + a_i, dp[1])$；</li>
<li>$s[i] == \text{'d'}$：$dp[3] \gets \min(dp[3] + a_i, dp[2])$。</li>
</ul>
<p>答案为 $dp[3]$。</p>
<h3>复杂度分析</h3>
<ul>
<li><strong>时间复杂度</strong>：$O(n)$。</li>
<li><strong>空间复杂度</strong>：$O(1)$。</li>
</ul>
<h3>参考代码</h3>
<pre><code>#include &lt;bits/stdc++.h&gt;
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n;
    if (!(cin &gt;&gt; n)) return 0;

    string s;
    cin &gt;&gt; s;

    vector&lt;long long&gt; a(n);
    for (int i = 0; i &lt; n; i++) cin &gt;&gt; a[i];

    vector&lt;long long&gt; dp(4, 0);

    for (int i = 0; i &lt; n; i++) {
        if (s[i] == 'h') {
            dp[0] += a[i];
        } else if (s[i] == 'a') {
            dp[1] = min(dp[1] + a[i], dp[0]);
        } else if (s[i] == 'r') {
            dp[2] = min(dp[2] + a[i], dp[1]);
        } else if (s[i] == 'd') {
            dp[3] = min(dp[3] + a[i], dp[2]);
        }
    }

    cout &lt;&lt; dp[3] &lt;&lt; "\n";
    return 0;
}
</code></pre>
<hr />
<h2>G. Catch Overflow! (<a href="https://codeforces.com/problemset/problem/1175/B">CodeForces 1175B</a>)</h2>
<ul>
<li><strong>标签</strong>：栈模拟、大数防溢出</li>
</ul>
<h3>题目大意</h3>
<p>初始 $x = 0$。程序包含 <code>for n</code>、<code>end</code>、<code>add</code>。<code>add</code> 会累加外层循环的连乘倍数。若 $x \ge 2^{32}$ 输出 <code>OVERFLOW!!!</code>，否则输出最终的 $x$。</p>
<h3>算法思路</h3>
<p>使用栈维护循环倍数的前缀积。遇到乘积超过 $2^{32}$ 时截断为 $2^{32}$ 防止越界。执行 <code>add</code> 时累加，若 $\ge 2^{32}$ 立即输出 <code>OVERFLOW!!!</code>。</p>
<h3>复杂度分析</h3>
<ul>
<li><strong>时间复杂度</strong>：$O(l)$。</li>
<li><strong>空间复杂度</strong>：$O(l)$。</li>
</ul>
<h3>参考代码</h3>
<pre><code>#include &lt;bits/stdc++.h&gt;
#define int long long
using namespace std;

const int max_ = 1LL &lt;&lt; 32;

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int l;
    if (!(cin &gt;&gt; l)) return 0;

    vector&lt;int&gt; stk;
    stk.reserve(l + 1);
    stk.push_back(1);

    int ans = 0;
    for (int i = 0; i &lt; l; i++) {
        string s;
        cin &gt;&gt; s;
        if (s == "for") {
            int n;
            cin &gt;&gt; n;
            stk.push_back(min(max_, stk.back() * n));
        } else if (s == "end") {
            stk.pop_back();
        } else if (s == "add") {
            ans += stk.back();
            if (ans &gt;= max_) {
                cout &lt;&lt; "OVERFLOW!!!\n";
                return 0;
            }
        }
    }

    cout &lt;&lt; ans &lt;&lt; "\n";
    return 0;
}
</code></pre>
<hr />
<h2>H. Magic Ship (<a href="https://codeforces.com/problemset/problem/1117/C">CodeForces 1117C</a>)</h2>
<ul>
<li><strong>标签</strong>：二分答案、前缀和、曼哈顿距离</li>
</ul>
<h3>题目大意</h3>
<p>起点 $(x_1, y_1)$，终点 $(x_2, y_2)$。风力周期为 $n$。每天风力推船 1 步，船自主可走 1 步或不动。求最少到达天数；无法到达输出 -1。</p>
<h3>算法思路</h3>
<p>二分天数 $mid$。计算前 $mid$ 天风力位移 $(wx, wy)$，风力吹到点为 $(cur_x, cur_y) = (x_1 + wx, y_1 + wy)$。
可行充要条件：
$$|x_2 - cur_x| + |y_2 - cur_y| \le mid$$
二分上界取 $10^{15}$。</p>
<h3>复杂度分析</h3>
<ul>
<li><strong>时间复杂度</strong>：$O(n + \log(\text{max_ans}))$。</li>
<li><strong>空间复杂度</strong>：$O(n)$。</li>
</ul>
<h3>参考代码</h3>
<pre><code>#include &lt;bits/stdc++.h&gt;
#define int long long
using namespace std;

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int x1, y1, x2, y2;
    if (!(cin &gt;&gt; x1 &gt;&gt; y1 &gt;&gt; x2 &gt;&gt; y2)) return 0;

    int n;
    string s;
    cin &gt;&gt; n &gt;&gt; s;

    if (x1 == x2 &amp;&amp; y1 == y2) {
        cout &lt;&lt; 0 &lt;&lt; "\n";
        return 0;
    }

    vector&lt;int&gt; px(n + 1, 0), py(n + 1, 0);
    for (int i = 0; i &lt; n; i++) {
        px[i + 1] = px[i];
        py[i + 1] = py[i];
        if (s[i] == 'U') py[i + 1]++;
        else if (s[i] == 'D') py[i + 1]--;
        else if (s[i] == 'L') px[i + 1]--;
        else if (s[i] == 'R') px[i + 1]++;
    }

    int l = 1, r = 1e15;
    int ans = -1;

    auto check = [&amp;](int mid) -&gt; bool {
        int cnt = mid / n, rem = mid % n;
        int wx = cnt * px[n] + px[rem], wy = cnt * py[n] + py[rem];
        int cur_x = x1 + wx, cur_y = y1 + wy;
        return mid &gt;= llabs(x2 - cur_x) + llabs(y2 - cur_y);
    };

    while (l &lt;= r) {
        int mid = l + (r - l) / 2;
        if (check(mid)) {
            ans = mid;
            r = mid - 1;
        } else {
            l = mid + 1;
        }
    }

    cout &lt;&lt; ans &lt;&lt; "\n";
    return 0;
}
</code></pre>
<hr />
<h2>I. Maximal Continuous Rest (<a href="https://codeforces.com/problemset/problem/1141/B">CodeForces 1141B</a>)</h2>
<ul>
<li><strong>标签</strong>：环形数组、断环成链</li>
</ul>
<h3>题目大意</h3>
<p>给定循环 0/1 数组，求最长连续 1 的长度。</p>
<h3>算法思路</h3>
<p>数组倍长为 $2n$，统计最大连续 1 计数，与 $n$ 取 $\min$。</p>
<h3>复杂度分析</h3>
<ul>
<li><strong>时间复杂度</strong>：$O(n)$。</li>
<li><strong>空间复杂度</strong>：$O(n)$。</li>
</ul>
<h3>参考代码</h3>
<pre><code>#include &lt;bits/stdc++.h&gt;
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n;
    if (!(cin &gt;&gt; n)) return 0;

    vector&lt;int&gt; a(2 * n);
    for (int i = 0; i &lt; n; i++) {
        cin &gt;&gt; a[i];
        a[i + n] = a[i];
    }

    int ans = 0, cur = 0;
    for (int i = 0; i &lt; 2 * n; i++) {
        if (a[i] == 1) {
            cur++;
            ans = max(ans, cur);
        } else {
            cur = 0;
        }
    }

    ans = min(ans, n);
    cout &lt;&lt; ans &lt;&lt; "\n";
    return 0;
}
</code></pre>
<hr />
<h2>J. Cow and Snacks (<a href="https://codeforces.com/problemset/problem/1209/D">CodeForces 1209D</a>)</h2>
<ul>
<li><strong>标签</strong>：图论、连通分量、并查集</li>
</ul>
<h3>题目大意</h3>
<p>$n$ 种零食，$k$ 位客人。每人喜好两种零食，若都已被拿走则伤心。求最少伤心人数。</p>
<h3>算法思路</h3>
<p>大小为 $V$ 的连通块最多满足 $V-1$ 人。使用并查集合并，每次成功合并说明增加 1 位满意客人。答案为 $k - \text{成功合并次数}$。</p>
<h3>复杂度分析</h3>
<ul>
<li><strong>时间复杂度</strong>：$O(k \cdot \alpha(n))$。</li>
<li><strong>空间复杂度</strong>：$O(n)$。</li>
</ul>
<h3>参考代码</h3>
<pre><code>#include &lt;bits/stdc++.h&gt;
using namespace std;

struct DSU {
    vector&lt;int&gt; parent;
    DSU(int n) : parent(n + 1) {
        iota(parent.begin(), parent.end(), 0);
    }
    int find(int i) {
        if (parent[i] == i)
            return i;
        return parent[i] = find(parent[i]);
    }
    bool unite(int i, int j) {
        int root_i = find(i);
        int root_j = find(j);
        if (root_i != root_j) {
            parent[root_i] = root_j;
            return true;
        }
        return false;
    }
};

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n, k;
    if (!(cin &gt;&gt; n &gt;&gt; k)) return 0;

    DSU dsu(n);
    int happy = 0;

    for (int i = 0; i &lt; k; i++) {
        int x, y;
        cin &gt;&gt; x &gt;&gt; y;
        if (dsu.unite(x, y)) {
            happy++;
        }
    }

    int sad = k - happy;
    cout &lt;&lt; sad &lt;&lt; "\n";
    return 0;
}
</code></pre>
<hr />
<h2>K. Array and Segments (Hard version) (<a href="https://codeforces.com/problemset/problem/1108/E2">CodeForces 1108E2</a>)</h2>
<ul>
<li><strong>标签</strong>：线段树、扫描线、贪心</li>
</ul>
<h3>题目大意</h3>
<p>选择区间子集执行 $-1$，最大化 $\max(a') - \min(a')$。</p>
<h3>算法思路</h3>
<p>若最小值在 $p$ 取得，最优策略是选择全部覆盖 $p$ 的区间。扫描线遍历 $p \in [1, n]$，在 $l_j$ 时线段树区间 $-1$，在 $r_j$ 之后区间 $+1$，全局维护 $\max - \min$。</p>
<h3>复杂度分析</h3>
<ul>
<li><strong>时间复杂度</strong>：$O(n + m \log n)$。</li>
<li><strong>空间复杂度</strong>：$O(n + m)$。</li>
</ul>
<h3>参考代码</h3>
<pre><code>#include &lt;bits/stdc++.h&gt;
using namespace std;

struct Node {
    int mn, mx, lazy;
};

const int INF = 1e9;
vector&lt;Node&gt; tree;
vector&lt;int&gt; a;

void push_up(int u) {
    tree[u].mn = min(tree[u &lt;&lt; 1].mn, tree[u &lt;&lt; 1 | 1].mn);
    tree[u].mx = max(tree[u &lt;&lt; 1].mx, tree[u &lt;&lt; 1 | 1].mx);
}

void apply(int u, int val) {
    tree[u].mn += val;
    tree[u].mx += val;
    tree[u].lazy += val;
}

void push_down(int u) {
    if (tree[u].lazy != 0) {
        apply(u &lt;&lt; 1, tree[u].lazy);
        apply(u &lt;&lt; 1 | 1, tree[u].lazy);
        tree[u].lazy = 0;
    }
}

void build(int u, int l, int r) {
    tree[u].lazy = 0;
    if (l == r) {
        tree[u].mn = a[l];
        tree[u].mx = a[l];
        return;
    }
    int mid = l + (r - l) / 2;
    build(u &lt;&lt; 1, l, mid);
    build(u &lt;&lt; 1 | 1, mid + 1, r);
    push_up(u);
}

void update(int u, int l, int r, int ql, int qr, int val) {
    if (ql &lt;= l &amp;&amp; r &lt;= qr) {
        apply(u, val);
        return;
    }
    push_down(u);
    int mid = l + (r - l) / 2;
    if (ql &lt;= mid) update(u &lt;&lt; 1, l, mid, ql, qr, val);
    if (qr &gt; mid) update(u &lt;&lt; 1 | 1, mid + 1, r, ql, qr, val);
    push_up(u);
}

struct Segment {
    int l, r, id;
};

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n, m;
    if (!(cin &gt;&gt; n &gt;&gt; m)) return 0;

    a.resize(n + 1);
    int orig_min = INF, orig_max = -INF;
    for (int i = 1; i &lt;= n; i++) {
        cin &gt;&gt; a[i];
        orig_min = min(orig_min, a[i]);
        orig_max = max(orig_max, a[i]);
    }

    vector&lt;Segment&gt; segs(m);
    vector&lt;vector&lt;int&gt;&gt; start_at(n + 2), end_at(n + 2);
    for (int i = 0; i &lt; m; i++) {
        cin &gt;&gt; segs[i].l &gt;&gt; segs[i].r;
        segs[i].id = i + 1;
        start_at[segs[i].l].push_back(i);
        end_at[segs[i].r].push_back(i);
    }

    tree.resize(4 * (n + 1));
    build(1, 1, n);

    int best_diff = orig_max - orig_min;
    int best_p = 0;

    for (int p = 1; p &lt;= n; p++) {
        for (int idx : start_at[p]) {
            update(1, 1, n, segs[idx].l, segs[idx].r, -1);
        }

        int cur_diff = tree[1].mx - tree[1].mn;
        if (cur_diff &gt; best_diff) {
            best_diff = cur_diff;
            best_p = p;
        }

        for (int idx : end_at[p]) {
            update(1, 1, n, segs[idx].l, segs[idx].r, 1);
        }
    }

    vector&lt;int&gt; chosen;
    if (best_p &gt; 0) {
        for (int i = 0; i &lt; m; i++) {
            if (segs[i].l &lt;= best_p &amp;&amp; best_p &lt;= segs[i].r) {
                chosen.push_back(segs[i].id);
            }
        }
    }

    cout &lt;&lt; best_diff &lt;&lt; "\n";
    cout &lt;&lt; chosen.size() &lt;&lt; "\n";
    for (int i = 0; i &lt; (int)chosen.size(); i++) {
        cout &lt;&lt; chosen[i] &lt;&lt; (i + 1 == (int)chosen.size() ? "" : " ");
    }
    cout &lt;&lt; "\n";

    return 0;
}
</code></pre>
<hr />
<h2>L. Vasya and Triangle (<a href="https://codeforces.com/problemset/problem/1030/D">CodeForces 1030D</a>)</h2>
<ul>
<li><strong>标签</strong>：数论、构造、几何</li>
</ul>
<h3>题目大意</h3>
<p>在 $n \times m$ 区域构造三角形使面积为 $\frac{nm}{k}$。</p>
<h3>算法思路</h3>
<p>必要条件 $k \mid 2nm$。构造直角边 $xy = \frac{2nm}{k}$。</p>
<ul>
<li>若 $k$ 为偶数：$x = \frac{n}{\gcd(n, k/2)}$，$y = \frac{m}{(k/2)/\gcd(n, k/2)}$；</li>
<li>若 $k$ 为奇数：求出基础值后将因子 2 乘给不超过边界的一维。</li>
</ul>
<h3>复杂度分析</h3>
<ul>
<li><strong>时间复杂度</strong>：$O(\log(\min(n, k)))$。</li>
<li><strong>空间复杂度</strong>：$O(1)$。</li>
</ul>
<h3>参考代码</h3>
<pre><code>#include &lt;bits/stdc++.h&gt;
#define int long long
using namespace std;

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n, m, k;
    if (!(cin &gt;&gt; n &gt;&gt; m &gt;&gt; k)) return 0;

    if ((2 * n * m) % k != 0) {
        cout &lt;&lt; "NO\n";
        return 0;
    }

    cout &lt;&lt; "YES\n";
    int x, y;
    if (k % 2 == 0) {
        int kp = k / 2;
        int g = gcd(n, kp);
        x = n / g;
        y = m / (kp / g);
    } else {
        int g = gcd(n, k);
        x = n / g;
        y = m / (k / g);
        if (2 * x &lt;= n) {
            x *= 2;
        } else {
            y *= 2;
        }
    }

    cout &lt;&lt; "0 0\n";
    cout &lt;&lt; x &lt;&lt; " 0\n";
    cout &lt;&lt; "0 " &lt;&lt; y &lt;&lt; "\n";

    return 0;
}
</code></pre>
<hr />
<h2>M. Two Arrays and Sum of Functions (<a href="https://codeforces.com/problemset/problem/1165/E">CodeForces 1165E</a>)</h2>
<ul>
<li><strong>标签</strong>：贡献法、排序不等式</li>
</ul>
<h3>题目大意</h3>
<p>重排数组 $b$，求 $\sum_{1 \le l \le r \le n} \sum_{i=l}^r a_i b_i$ 模 998244353 的最大值。</p>
<h3>算法思路</h3>
<p>计算贡献 $w_i = a_i \cdot i \cdot (n - i + 1)$。
排序不等式：将 $w$ 和 $b$ 均升序排序后对应相乘。注意排序前切勿取模。</p>
<h3>复杂度分析</h3>
<ul>
<li><strong>时间复杂度</strong>：$O(n \log n)$。</li>
<li><strong>空间复杂度</strong>：$O(n)$。</li>
</ul>
<h3>参考代码</h3>
<pre><code>#include &lt;bits/stdc++.h&gt;
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n;
    if (!(cin &gt;&gt; n)) return 0;

    vector&lt;long long&gt; a(n), b(n);
    for (int i = 0; i &lt; n; i++) cin &gt;&gt; a[i];
    for (int i = 0; i &lt; n; i++) cin &gt;&gt; b[i];

    vector&lt;long long&gt; w(n);
    for (int i = 0; i &lt; n; i++) {
        w[i] = a[i] * (i + 1LL) * (n - i);
    }

    sort(w.begin(), w.end());
    sort(b.begin(), b.end());

    const long long MOD = 998244353;
    long long ans = 0;
    for (int i = 0; i &lt; n; i++) {
        long long term = (w[i] % MOD) * (b[i] % MOD) % MOD;
        ans = (ans + term) % MOD;
    }

    cout &lt;&lt; ans &lt;&lt; "\n";
    return 0;
}
</code></pre>
]]></content>
    <author><name>ZXHYCZX</name></author>
    <category term="算法竞赛"/>
  </entry>
  <entry>
    <title>异或在算法竞赛中的应用</title>
    <link href="https://zxhyczx.github.io/blog/posts/xor-applications/" rel="alternate" type="text/html"/>
    <id>https://zxhyczx.github.io/blog/posts/xor-applications/</id>
    <published>2026-08-03T00:00:00.000Z</published>
    <updated>2026-08-03T00:00:00.000Z</updated>
    <summary>从异或的基本性质、前缀异或，到 01 Trie、线性基、环空间、Nim 博弈与 XOR-FWT 的完整总结。</summary>
    <content type="html"><![CDATA[<h1>异或在算法竞赛中的应用</h1>
<p>注：本文部分内容由AI润色排版</p>
<p>异或，英文为 Exclusive OR，通常简写为 XOR。在 C++ 中，异或运算符写作 <code>^</code>。</p>
<p>对于刚接触算法竞赛的选手来说，异或题经常给人一种“技巧性很强”的感觉：有些题只需要把所有数字异或一遍，有些题却需要使用 01 Trie、可持久化数据结构，甚至线性基和快速沃尔什变换。</p>
<p>这些题目看似相差很远，实际上可以归纳为三类基本思想：</p>
<ol>
<li><strong>抵消性</strong>：相同数字异或两次会消失；</li>
<li><strong>按位决策</strong>：异或值的大小首先取决于最高不同位；</li>
<li><strong>线性结构</strong>：异或可以看作二元域上的向量加法。</li>
</ol>
<h2>一、异或的基本性质</h2>
<h3>1. 单个位上的异或</h3>
<p>对于两个二进制位，异或运算规则如下：</p>
<table>
<thead>
<tr>
<th>$a$</th>
<th>$b$</th>
<th>$a\oplus b$</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>0</td>
<td>0</td>
</tr>
<tr>
<td>0</td>
<td>1</td>
<td>1</td>
</tr>
<tr>
<td>1</td>
<td>0</td>
<td>1</td>
</tr>
<tr>
<td>1</td>
<td>1</td>
<td>0</td>
</tr>
</tbody>
</table>
<p>也就是说：</p>
<blockquote>
<p>两个二进制位相同时结果为 $0$，不同时结果为 $1$。</p>
</blockquote>
<p>例如：</p>
<pre><code>  101101
^ 011001
--------
  110100
</code></pre>
<p>在 C++ 中可以写成：</p>
<pre><code>int c = a ^ b;
</code></pre>
<h3>2. 基本代数性质</h3>
<p>异或满足以下性质：</p>
<p>$$
a\oplus 0=a
$$</p>
<p>$$
a\oplus a=0
$$</p>
<p>$$
a\oplus b=b\oplus a
$$</p>
<p>$$
(a\oplus b)\oplus c=a\oplus(b\oplus c)
$$</p>
<p>由此可以得到：</p>
<p>$$
a\oplus b\oplus b=a
$$</p>
<p>这意味着异或的逆运算仍然是异或。</p>
<p>若：</p>
<p>$$
a\oplus b=c
$$</p>
<p>那么：</p>
<p>$$
a\oplus c=b
$$</p>
<p>以及：</p>
<p>$$
b\oplus c=a
$$</p>
<p>因此，在处理异或等式时，也可以进行类似“移项”的操作，只不过移项后使用的仍然是异或。</p>
<h3>3. 相同元素的抵消</h3>
<p>考虑下面的序列：</p>
<pre><code>3, 5, 3, 7, 5
</code></pre>
<p>将所有数字异或：</p>
<p>$$
3\oplus5\oplus3\oplus7\oplus5
$$</p>
<p>利用交换律和结合律重新排列：</p>
<p>$$
(3\oplus3)\oplus(5\oplus5)\oplus7
$$</p>
<p>相同数字两两抵消：</p>
<p>$$
0\oplus0\oplus7=7
$$</p>
<p>因此，如果一个数组中只有一个数字出现奇数次，其余数字都出现偶数次，那么将所有元素异或起来，就能得到那个特殊数字。</p>
<pre><code>int answer = 0;

for (int x : a) {
    answer ^= x;
}
</code></pre>
<p>时间复杂度为 $O(n)$，额外空间复杂度为 $O(1)$。</p>
<blockquote>
<p><strong>参考与练习</strong></p>
<ul>
<li><a href="https://oi-wiki.org/math/bit/">OI Wiki：位操作</a></li>
<li><a href="https://www.luogu.com.cn/problem/P1469">洛谷 P1469 找筷子</a>：利用相同元素的抵消性质寻找未配对元素。</li>
<li><a href="https://codeforces.com/problemset/problem/276/D">Codeforces 276D — Little Girl and Maximum XOR</a>：从最高不同位分析区间中的最大异或值。</li>
</ul>
</blockquote>
<hr />
<h2>二、异或方程与计数</h2>
<p>考虑下面的问题：</p>
<blockquote>
<p>给定一个数组 $a$ 和整数 $k$，统计有多少对下标 $i&lt;j$，满足</p>
<p>$$
a_i\oplus a_j=k
$$</p>
</blockquote>
<p>利用异或移项：</p>
<p>$$
a_i\oplus a_j=k
$$</p>
<p>两边同时异或 $a_j$，得到：</p>
<p>$$
a_i=a_j\oplus k
$$</p>
<p>因此，当我们扫描到 $a_j$ 时，只需要查询此前出现过多少个：</p>
<p>$$
a_j\oplus k
$$</p>
<p>可以使用哈希表记录每个数字已经出现的次数：</p>
<pre><code>long long countPairs(const vector&lt;int&gt;&amp; a, int k) {
    unordered_map&lt;int, long long&gt; count;
    long long answer = 0;

    for (int x : a) {
        answer += count[x ^ k];
        ++count[x];
    }

    return answer;
}
</code></pre>
<p>这里必须先查询，再插入当前元素。</p>
<p>因为哈希表中只应保存当前位置之前的元素，这样才能保证统计的是满足 $i&lt;j$ 的数对。</p>
<p>这种问题可以总结为：</p>
<blockquote>
<p>当题目给出了两个数的异或结果，并要求寻找另一个数时，可以优先尝试使用异或移项。</p>
</blockquote>
<blockquote>
<p><strong>参考与练习</strong></p>
<ul>
<li><a href="https://codeforces.com/problemset/problem/742/B">Codeforces 742B — Arpa’s obvious problem and Mehrdad’s terrible solution</a>：异或方程与数对计数的经典应用。</li>
</ul>
</blockquote>
<hr />
<h2>三、前缀异或</h2>
<h3>1. 区间异或</h3>
<p>定义前缀异或：</p>
<p>$$
pre_i=a_1\oplus a_2\oplus\cdots\oplus a_i
$$</p>
<p>并令：</p>
<p>$$
pre_0=0
$$</p>
<p>那么区间 $[l,r]$ 的异或值为：</p>
<p>$$
a_l\oplus a_{l+1}\oplus\cdots\oplus a_r
=pre_r\oplus pre_{l-1}
$$</p>
<p>展开右侧：</p>
<p>$$
pre_r\oplus pre_{l-1}
$$</p>
<p>$$</p>
<p>(a_1\oplus\cdots\oplus a_{l-1}\oplus a_l\oplus\cdots\oplus a_r)
\oplus
(a_1\oplus\cdots\oplus a_{l-1})
$$</p>
<p>前面的公共部分各出现两次，全部抵消，最终只剩下：</p>
<p>$$
a_l\oplus a_{l+1}\oplus\cdots\oplus a_r
$$</p>
<p>代码与前缀和非常相似：</p>
<pre><code>vector&lt;int&gt; prefixXor(n + 1, 0);

for (int i = 1; i &lt;= n; ++i) {
    prefixXor[i] = prefixXor[i - 1] ^ a[i];
}

int rangeXor = prefixXor[r] ^ prefixXor[l - 1];
</code></pre>
<h3>2. 统计异或值为 $k$ 的子数组</h3>
<p>现在希望统计满足：</p>
<p>$$
a_l\oplus a_{l+1}\oplus\cdots\oplus a_r=k
$$</p>
<p>的区间数量。</p>
<p>利用前缀异或：</p>
<p>$$
pre_r\oplus pre_{l-1}=k
$$</p>
<p>移项可得：</p>
<p>$$
pre_{l-1}=pre_r\oplus k
$$</p>
<p>因此，在扫描到当前前缀异或值 $pre_r$ 时，只需要统计此前出现过多少次：</p>
<p>$$
pre_r\oplus k
$$</p>
<pre><code>long long countSubarraysXorK(const vector&lt;int&gt;&amp; a, int k) {
    unordered_map&lt;int, long long&gt; count;

    // 空前缀 pre[0] = 0
    count[0] = 1;

    int prefix = 0;
    long long answer = 0;

    for (int x : a) {
        prefix ^= x;

        answer += count[prefix ^ k];
        ++count[prefix];
    }

    return answer;
}
</code></pre>
<p>初始化：</p>
<pre><code>count[0] = 1;
</code></pre>
<p>表示空前缀。</p>
<p>如果从数组开头到当前位置的异或值本身等于 $k$，那么：</p>
<p>$$
prefix\oplus k=0
$$</p>
<p>此时空前缀应当作为一个合法选择被统计。</p>
<h3>3. 树上的路径异或</h3>
<p>下面讨论的是<strong>无向带权树</strong>，路径的权值定义为路径上所有边权的异或值。</p>
<p>任选一个节点作为根，定义：</p>
<p>$$
dis_u
$$</p>
<p>表示根节点到节点 $u$ 的路径异或值。</p>
<p>如果边 $(u,v)$ 的权值为 $w$，那么在 DFS 过程中：</p>
<pre><code>dis[v] = dis[u] ^ w;
</code></pre>
<p>对于任意两个节点 $u,v$，它们之间路径的异或值为：</p>
<p>$$
path(u,v)=dis_u\oplus dis_v
$$</p>
<p>原因是根到 $u$ 与根到 $v$ 的公共部分会出现两次，从而相互抵消。</p>
<p>这和数组中的：</p>
<p>$$
pre_r\oplus pre_{l-1}
$$</p>
<p>本质上是同一种结构。</p>
<blockquote>
<p><strong>参考与练习</strong></p>
<ul>
<li><a href="https://oi-wiki.org/math/bit/">OI Wiki：位操作</a></li>
<li><a href="https://www.luogu.com.cn/problem/P2420">洛谷 P2420 让我们异或吧</a>：无向带权树上的路径异或查询。</li>
</ul>
</blockquote>
<hr />
<h2>四、01 Trie 与异或极值</h2>
<h3>1. 最大异或问题</h3>
<p>考虑经典问题：</p>
<blockquote>
<p>给定若干非负整数，对于一个查询值 $x$，寻找集合中的一个数 $y$，使得 $x\oplus y$ 最大。</p>
</blockquote>
<p>整数的大小首先由二进制表示中的最高不同位决定，因此应从最高位向最低位考虑。</p>
<p>假设 $x$ 当前位为 $0$：</p>
<ul>
<li>如果 $y$ 当前位为 $1$，这一位的异或结果为 $1$；</li>
<li>如果 $y$ 当前位为 $0$，这一位的异或结果为 $0$。</li>
</ul>
<p>为了使最终答案尽可能大，应优先选择当前位与 $x$ 不同的数字。</p>
<p>可以把所有数字的二进制表示插入一棵 Trie：</p>
<ul>
<li>每一层对应一个二进制位；</li>
<li><code>child[0]</code> 表示这一位是 $0$；</li>
<li><code>child[1]</code> 表示这一位是 $1$；</li>
<li>查询时优先进入与 $x$ 当前位相反的分支。</li>
</ul>
<h3>2. 基础模板</h3>
<p>下面的模板假设输入为不超过 $2^{31}-1$ 的非负整数，因此处理第 $30$ 位到第 $0$ 位。</p>
<p>实际使用时，应根据题目数据范围调整最高位。</p>
<pre><code>struct BinaryTrie {
    static constexpr int MAX_BIT = 30;

    struct Node {
        int child[2] = {-1, -1};
        int count = 0;
    };

    vector&lt;Node&gt; trie;

    BinaryTrie() {
        trie.emplace_back();
    }

    bool empty() const {
        return trie[0].count == 0;
    }

    void insert(int x) {
        int node = 0;
        ++trie[node].count;

        for (int bit = MAX_BIT; bit &gt;= 0; --bit) {
            int value = (x &gt;&gt; bit) &amp; 1;

            if (trie[node].child[value] == -1) {
                trie[node].child[value] =
                    static_cast&lt;int&gt;(trie.size());
                trie.emplace_back();
            }

            node = trie[node].child[value];
            ++trie[node].count;
        }
    }

    int queryMaxXor(int x) const {
        // 调用前必须保证 Trie 非空
        int node = 0;
        int answer = 0;

        for (int bit = MAX_BIT; bit &gt;= 0; --bit) {
            int value = (x &gt;&gt; bit) &amp; 1;
            int preferred = value ^ 1;

            int preferredChild = trie[node].child[preferred];

            if (preferredChild != -1 &amp;&amp;
                trie[preferredChild].count &gt; 0) {
                answer |= 1 &lt;&lt; bit;
                node = preferredChild;
            } else {
                int fallbackChild = trie[node].child[value];
                node = fallbackChild;
            }
        }

        return answer;
    }
};
</code></pre>
<p>每次插入或查询都会经过固定数量的二进制位，因此复杂度为：</p>
<p>$$
O(B)
$$</p>
<p>其中 $B$ 是处理的二进制位数。</p>
<p>处理前缀异或问题时，通常需要在开始查询前先插入前缀异或值 $0$。</p>
<h3>3. 动态插入与删除</h3>
<p>如果题目要求支持删除，不能直接删除 Trie 节点，因为同一个前缀可能被多个数字共享。</p>
<p>正确做法是在每个节点维护经过该节点的数字数量。</p>
<pre><code>void add(int x, int delta) {
    int node = 0;
    trie[node].count += delta;

    for (int bit = MAX_BIT; bit &gt;= 0; --bit) {
        int value = (x &gt;&gt; bit) &amp; 1;

        if (trie[node].child[value] == -1) {
            trie[node].child[value] =
                static_cast&lt;int&gt;(trie.size());
            trie.emplace_back();
        }

        node = trie[node].child[value];
        trie[node].count += delta;
    }
}
</code></pre>
<p>其中：</p>
<ul>
<li><code>delta = 1</code> 表示插入；</li>
<li><code>delta = -1</code> 表示删除。</li>
</ul>
<p>执行删除操作时，必须保证集合中至少存在一个对应的数字。如果题目不保证这一点，还需要额外维护每个完整数字的出现次数。</p>
<h3>4. 最大异或与最小异或</h3>
<p>求最大异或时，应优先选择相反位：</p>
<pre><code>preferred = bit ^ 1
</code></pre>
<p>求最小异或时，应优先选择相同位：</p>
<pre><code>preferred = bit
</code></pre>
<p>两种操作的本质相同，区别只在于每一层优先选择哪个分支。</p>
<blockquote>
<p><strong>参考与练习</strong></p>
<ul>
<li><a href="https://oi-wiki.org/string/trie/">OI Wiki：字典树 Trie</a></li>
<li><a href="https://www.luogu.com.cn/problem/P4551">洛谷 P4551 最长异或路径</a>：树上前缀异或与最大异或查询。</li>
<li><a href="https://codeforces.com/problemset/problem/706/D">Codeforces 706D — Vasiliy’s Multiset</a>：动态插入、删除与最大异或查询。</li>
<li><a href="https://codeforces.com/problemset/problem/923/C">Codeforces 923C — Perfect Security</a>：可删除多重集合中的最小异或匹配。</li>
</ul>
</blockquote>
<hr />
<h2>五、Trie 上统计 $x\oplus y&lt;k$</h2>
<p>最大异或查询只需要决定当前位应优先选择相同还是不同。</p>
<p>但有些题目要求统计：</p>
<p>$$
x\oplus y&lt;k
$$</p>
<p>此时需要同时考虑 $x$、$y$ 和 $k$ 的当前位。</p>
<p>令：</p>
<p>$$
z=x\oplus y
$$</p>
<p>问题转化为在 Trie 中统计满足：</p>
<p>$$
z&lt;k
$$</p>
<p>的 $y$ 的数量。</p>
<p>仍然从最高位向最低位处理。</p>
<h3>1. 当 $k$ 当前位为 $0$</h3>
<p>如果此前各位仍然与 $k$ 相等，那么为了保证结果不超过 $k$，$z$ 当前位只能为 $0$。</p>
<p>因此，$y$ 当前位必须与 $x$ 当前位相同，并继续向低位判断。</p>
<h3>2. 当 $k$ 当前位为 $1$</h3>
<p>此时有两种选择。</p>
<p>如果令 $z$ 当前位为 $0$，那么答案在这一位已经严格小于 $k$，后续所有位都可以任意选择。因此，可以直接加入对应子树中的所有元素数量。</p>
<p>如果令 $z$ 当前位为 $1$，当前前缀仍然与 $k$ 相等，需要继续向低位判断。</p>
<pre><code>long long countLess(int x, int k) const {
    int node = 0;
    long long answer = 0;

    for (int bit = MAX_BIT; bit &gt;= 0; --bit) {
        if (node == -1) {
            break;
        }

        int xb = (x &gt;&gt; bit) &amp; 1;
        int kb = (k &gt;&gt; bit) &amp; 1;

        if (kb == 1) {
            // 令 xor 当前位为 0，此后一定小于 k
            int same = trie[node].child[xb];

            if (same != -1) {
                answer += trie[same].count;
            }

            // 令 xor 当前位为 1，继续保持前缀相等
            node = trie[node].child[xb ^ 1];
        } else {
            // xor 当前位只能为 0
            node = trie[node].child[xb];
        }
    }

    return answer;
}
</code></pre>
<p>如果题目要求统计：</p>
<p>$$
x\oplus y\geq k
$$</p>
<p>可以先计算集合中的元素总数，再减去满足：</p>
<p>$$
x\oplus y&lt;k
$$</p>
<p>的元素数量。</p>
<blockquote>
<p><strong>参考与练习</strong></p>
<ul>
<li><a href="https://oi-wiki.org/string/trie/">OI Wiki：字典树 Trie</a></li>
<li><a href="https://codeforces.com/problemset/problem/817/E">Codeforces 817E — Choosing The Commander</a>：动态集合中的异或上界统计。</li>
<li><a href="https://codeforces.com/problemset/problem/665/E">Codeforces 665E — Beautiful Subarrays</a>：前缀异或与 Trie 统计的结合。</li>
</ul>
</blockquote>
<hr />
<h2>六、可持久化 01 Trie</h2>
<h3>1. 普通 Trie 的局限</h3>
<p>普通 Trie 可以回答：</p>
<blockquote>
<p>在当前集合中，哪个数字与 $x$ 的异或值最大？</p>
</blockquote>
<p>但是，如果查询变为：</p>
<blockquote>
<p>只允许选择编号位于 $[l,r]$ 中的元素。</p>
</blockquote>
<p>普通 Trie 就无法直接判断某个分支中是否存在编号属于目标区间的元素。</p>
<p>一种常见解决方法是：为每个前缀建立一个 Trie 版本。</p>
<h3>2. 可持久化的基本思想</h3>
<p>假设已经有表示前 $i-1$ 个元素的 Trie。</p>
<p>插入第 $i$ 个元素时：</p>
<ul>
<li>不复制整棵 Trie；</li>
<li>只复制从根节点到该数字叶节点的路径；</li>
<li>没有变化的子树继续与旧版本共享。</li>
</ul>
<p>如果每个数字有 $B$ 个二进制位，那么每次插入只会新增：</p>
<p>$$
O(B)
$$</p>
<p>个节点。</p>
<p>因此，插入 $n$ 个数字的总空间复杂度约为：</p>
<p>$$
O(nB)
$$</p>
<h3>3. 区间查询与版本作差</h3>
<p>设：</p>
<ul>
<li><code>root[r]</code> 表示插入前 $r$ 个元素后的 Trie；</li>
<li><code>root[l - 1]</code> 表示插入前 $l-1$ 个元素后的 Trie。</li>
</ul>
<p>某个节点在编号区间 $[l,r]$ 中的元素数量，可以通过两个版本的节点计数相减得到：</p>
<p>$$
count(root_r)-count(root_{l-1})
$$</p>
<p>查询最大异或时，仍然优先尝试相反位，但只有当该分支在两个版本之间的数量差大于零时，才能进入该分支。</p>
<p>这一思想与主席树查询区间信息时的“版本作差”非常相似。</p>
<h3>4. P4735 中的前缀异或转化</h3>
<p>设序列的前缀异或为：</p>
<p>$$
s_i=a_1\oplus a_2\oplus\cdots\oplus a_i
$$</p>
<p>并令：</p>
<p>$$
s_0=0
$$</p>
<p>如果需要在 $p\in[l,r]$ 中选择一个位置，最大化：</p>
<p>$$
x\oplus a_p\oplus a_{p+1}\oplus\cdots\oplus a_n
$$</p>
<p>那么：</p>
<p>$$
a_p\oplus a_{p+1}\oplus\cdots\oplus a_n
=s_n\oplus s_{p-1}
$$</p>
<p>原式变为：</p>
<p>$$
x\oplus s_n\oplus s_{p-1}
$$</p>
<p>其中：</p>
<p>$$
x\oplus s_n
$$</p>
<p>在当前查询中是固定值。</p>
<p>因此，问题变成：</p>
<blockquote>
<p>在前缀编号 $p-1\in[l-1,r-1]$ 中选择一个 $s_{p-1}$，使它与固定值 $x\oplus s_n$ 的异或最大。</p>
</blockquote>
<p>这正是带编号区间限制的最大异或查询，因此可以使用可持久化 01 Trie。</p>
<blockquote>
<p><strong>参考与练习</strong></p>
<ul>
<li><a href="https://oi-wiki.org/ds/persistent-trie/">OI Wiki：可持久化字典树</a></li>
<li><a href="https://www.luogu.com.cn/problem/P4735">洛谷 P4735 最大异或和</a>：动态添加元素与带编号范围的最大异或查询。</li>
</ul>
</blockquote>
<hr />
<h2>七、异或线性基</h2>
<p>01 Trie 通常解决的是：</p>
<blockquote>
<p>从集合中选择一个数，与给定值进行异或。</p>
</blockquote>
<p>线性基解决的则是：</p>
<blockquote>
<p>从集合中选择任意多个数，它们能够异或出哪些结果？</p>
</blockquote>
<p>这是两类不同的问题。</p>
<h3>1. 将整数看作向量</h3>
<p>一个二进制整数可以看作由 $0$ 和 $1$ 构成的向量。</p>
<p>例如：</p>
<pre><code>13 = 1101
10 = 1010
 7 = 0111
</code></pre>
<p>异或相当于每一位在模 $2$ 意义下相加：</p>
<pre><code>0 + 0 = 0
0 + 1 = 1
1 + 0 = 1
1 + 1 = 0
</code></pre>
<p>因此，异或可以看作二元域 $\mathrm{GF}(2)$ 上的向量加法。</p>
<p>线性基就是从原集合中选出一组线性无关向量，使原集合能够异或得到的所有结果，都可以由这组基向量表示。</p>
<h3>2. 插入一个数字</h3>
<p>维护：</p>
<pre><code>basis[bit]
</code></pre>
<p>表示一个最高有效位为 <code>bit</code> 的基向量。</p>
<p>插入 $x$ 时，从最高位向最低位检查。</p>
<p>如果 $x$ 的第 <code>bit</code> 位为 $0$，无需处理。</p>
<p>如果该位为 $1$：</p>
<ul>
<li>如果 <code>basis[bit]</code> 为空，就把 $x$ 放入该位置；</li>
<li>否则令 $x$ 异或 <code>basis[bit]</code>，消去当前最高位。</li>
</ul>
<p>下面的代码假设输入不超过 $10^{18}$，因此处理第 $60$ 位到第 $0$ 位。</p>
<pre><code>#include &lt;cstdint&gt;

using u64 = std::uint64_t;

struct LinearBasis {
    static constexpr int MAX_BIT = 60;

    u64 basis[MAX_BIT + 1]{};

    bool insert(u64 x) {
        for (int bit = MAX_BIT; bit &gt;= 0; --bit) {
            if (((x &gt;&gt; bit) &amp; 1ULL) == 0) {
                continue;
            }

            if (basis[bit] == 0) {
                basis[bit] = x;
                return true;
            }

            x ^= basis[bit];
        }

        return false;
    }
};
</code></pre>
<p>如果最终 $x=0$，说明原来的 $x$ 可以由已有基向量异或得到，因此它与已有向量线性相关。</p>
<h3>3. 最大子集异或</h3>
<p>从最高位向最低位尝试使用每个基向量：</p>
<pre><code>u64 getMax() const {
    u64 answer = 0;

    for (int bit = MAX_BIT; bit &gt;= 0; --bit) {
        answer = max(answer, answer ^ basis[bit]);
    }

    return answer;
}
</code></pre>
<p>这里仍然是在进行最高位贪心。</p>
<p>如果：</p>
<pre><code>answer ^ basis[bit] &gt; answer
</code></pre>
<p>说明加入该基向量后，答案在某个关键高位上变大，因此应保留这次变化。</p>
<h3>4. 秩与方案数量</h3>
<p>假设原集合共有 $n$ 个数字，将它们视为 $\mathrm{GF}(2)$ 上的向量，其秩为 $r$。</p>
<p>那么线性张成空间中一共有：</p>
<p>$$
2^r
$$</p>
<p>个不同的异或结果。</p>
<p>从所有 $2^n$ 个子集中选择一个子集，相当于从 $\mathrm{GF}(2)^n$ 中选择一组系数。</p>
<p>从系数空间到异或结果空间的线性映射，其核的维数为：</p>
<p>$$
n-r
$$</p>
<p>因此，对于任意一个可以被表示的目标异或值，恰好有：</p>
<p>$$
2^{n-r}
$$</p>
<p>个子集能够得到该值，其中包括空集。</p>
<p>如果目标值不在线性张成空间中，则方案数为零。</p>
<blockquote>
<p><strong>参考与练习</strong></p>
<ul>
<li><a href="https://oi-wiki.org/math/linear-algebra/basis/">OI Wiki：线性基</a></li>
<li><a href="https://www.luogu.com.cn/problem/P3812">洛谷 P3812【模板】线性基</a>：最大子集异或。</li>
<li><a href="https://www.luogu.com.cn/problem/P3857">洛谷 P3857 彩灯</a>：利用线性基的秩计算可达到状态数量。</li>
<li><a href="https://www.luogu.com.cn/problem/P4570">洛谷 P4570 元素</a>：带权选择与线性无关性。</li>
<li><a href="https://codeforces.com/problemset/problem/959/F">Codeforces 959F — Mahmoud and Ehab and yet another xor task</a>：可表示性、秩与方案数。</li>
</ul>
</blockquote>
<hr />
<h2>八、图上的异或与环空间</h2>
<p>下面讨论的是<strong>无向带权图</strong>。起点与终点需要位于同一个连通分量中，路径权值定义为经过边权的异或值。</p>
<h3>1. 树上的情况</h3>
<p>在树上，两个节点之间的路径唯一。</p>
<p>定义根节点到 $u$ 的树上路径异或值为 $dis_u$，那么：</p>
<p>$$
path(u,v)=dis_u\oplus dis_v
$$</p>
<p>只需要一次 DFS 就能计算所有 $dis$。</p>
<h3>2. 一般图中的环</h3>
<p>在一般图中，两个节点之间可能存在多条不同路径。</p>
<p>先任选一棵 DFS 生成树，并计算：</p>
<p>$$
dis_u
$$</p>
<p>表示根节点到 $u$ 的树上路径异或值。</p>
<p>对于一条连接 $u,v$、边权为 $w$ 的非树边，它与生成树中的路径共同构成一个环，其异或值为：</p>
<p>$$
cycle=dis_u\oplus dis_v\oplus w
$$</p>
<p>其中：</p>
<ul>
<li>$dis_u$ 表示根到 $u$ 的树上路径；</li>
<li>$w$ 表示边 $(u,v)$；</li>
<li>$dis_v$ 表示根到 $v$ 的树上路径。</li>
</ul>
<p>这三部分共同组成一个闭合结构。</p>
<h3>3. 环空间与路径调整</h3>
<p>假设已经得到一条从起点 $s$ 到终点 $t$ 的基础路径，其异或值为：</p>
<p>$$
d=dis_s\oplus dis_t
$$</p>
<p>图中的其他可行路线与这条基础路线之间的差异，可以由若干个环的异或组合产生。</p>
<p>因此，其他路径的异或值可以表示为：</p>
<p>$$
d\oplus c_1\oplus c_2\oplus\cdots
$$</p>
<p>其中每个 $c_i$ 都来自图中的环空间。</p>
<p>将所有环异或值插入线性基之后，就可以使用线性基最大化或最小化基础路径异或值。</p>
<pre><code>u64 maximize(u64 x) const {
    for (int bit = MAX_BIT; bit &gt;= 0; --bit) {
        x = max(x, x ^ basis[bit]);
    }

    return x;
}
</code></pre>
<blockquote>
<p><strong>参考与练习</strong></p>
<ul>
<li><a href="https://oi-wiki.org/math/linear-algebra/basis/">OI Wiki：线性基</a></li>
<li><a href="https://www.luogu.com.cn/problem/P4151">洛谷 P4151 最大 XOR 和路径</a>：生成树、环异或与线性基。</li>
<li><a href="https://codeforces.com/problemset/problem/845/G">Codeforces 845G — Shortest Path Problem?</a>：使用环空间处理最小异或路径。</li>
<li><a href="https://codeforces.com/problemset/problem/724/G">Codeforces 724G — Xor-matic Number of the Graph</a>：环空间、线性基与按位贡献统计。</li>
</ul>
</blockquote>
<hr />
<h2>九、扩展：带位置的前缀线性基</h2>
<p>普通线性基维护的是整个集合。</p>
<p>如果题目要求回答：</p>
<blockquote>
<p>在区间 $[l,r]$ 中任选若干个数，求最大子集异或。</p>
</blockquote>
<p>就需要限制基向量所使用元素的位置。</p>
<p>一种经典方法是给每个基向量附加一个时间戳：</p>
<pre><code>basis[bit]：基向量的值
position[bit]：该基向量保留的最新位置
</code></pre>
<p>插入位于位置 $pos$ 的新元素时，从高位向低位消元。</p>
<p>如果新元素的位置比当前基向量记录的位置更靠后，就交换：</p>
<pre><code>当前向量 x、当前时间戳 pos
</code></pre>
<p>与：</p>
<pre><code>basis[bit]、position[bit]
</code></pre>
<p>这样可以在保持线性张成空间不变的前提下，让线性基尽量保留出现位置更靠后的向量。</p>
<p>查询区间左端点为 $l$ 时，只使用满足：</p>
<p>$$
position[bit]\geq l
$$</p>
<p>的基向量。</p>
<p>需要注意的是，被换出的旧向量不能直接丢弃。它仍然可能包含低位上的有效信息，因此必须继续向更低位进行消元。</p>
<p>这一结构常被称为前缀线性基或带位置线性基，适合处理离线区间最大子集异或问题。</p>
<blockquote>
<p><strong>参考与练习</strong></p>
<ul>
<li><a href="https://oi-wiki.org/math/linear-algebra/basis/">OI Wiki：线性基</a></li>
<li><a href="https://codeforces.com/problemset/problem/1100/F">Codeforces 1100F — Ivan and Burgers</a>：区间最大子集异或。</li>
<li><a href="https://www.luogu.com.cn/problem/P3292">洛谷 P3292 幸运数字</a>：树上路径与线性基合并。</li>
</ul>
</blockquote>
<hr />
<h2>十、Nim 游戏与异或和</h2>
<h3>1. 游戏规则</h3>
<p>经典 Nim 游戏有若干堆石子。</p>
<p>两名玩家轮流操作，每次可以：</p>
<ul>
<li>选择任意一堆；</li>
<li>从中取走任意多个石子；</li>
<li>但至少取走一个。</li>
</ul>
<p>无法继续操作的玩家失败。</p>
<p>设各堆石子数量为：</p>
<p>$$
a_1,a_2,\ldots,a_n
$$</p>
<p>定义 Nim 和：</p>
<p>$$
S=a_1\oplus a_2\oplus\cdots\oplus a_n
$$</p>
<p>结论为：</p>
<ul>
<li>如果 $S=0$，当前玩家必败；</li>
<li>如果 $S\neq0$，当前玩家必胜。</li>
</ul>
<h3>2. 为什么 $S=0$ 是必败状态</h3>
<p>假设当前 Nim 和为零。</p>
<p>玩家将某一堆 $a_i$ 修改为 $b_i$，且：</p>
<p>$$
a_i\neq b_i
$$</p>
<p>新的异或和为：</p>
<p>$$
0\oplus a_i\oplus b_i
=a_i\oplus b_i
$$</p>
<p>由于：</p>
<p>$$
a_i\neq b_i
$$</p>
<p>因此：</p>
<p>$$
a_i\oplus b_i\neq0
$$</p>
<p>所以，从异或和为零的状态出发，无论如何操作，都会进入异或和非零的状态。</p>
<h3>3. 为什么 $S\neq0$ 时可以变成零</h3>
<p>设 $S$ 的最高有效位为第 $k$ 位。</p>
<p>因为 $S$ 的第 $k$ 位是 $1$，所以至少存在一堆 $a_i$，其第 $k$ 位也是 $1$。</p>
<p>令：</p>
<p>$$
b_i=a_i\oplus S
$$</p>
<p>由于 $S$ 的最高有效位会把 $a_i$ 的第 $k$ 位从 $1$ 变成 $0$，因此：</p>
<p>$$
b_i&lt;a_i
$$</p>
<p>这是一次合法的减少操作。</p>
<p>操作后的 Nim 和为：</p>
<p>$$
S\oplus a_i\oplus b_i
$$</p>
<p>代入：</p>
<p>$$
b_i=a_i\oplus S
$$</p>
<p>得到：</p>
<p>$$
S\oplus a_i\oplus a_i\oplus S=0
$$</p>
<p>因此，异或和非零的玩家总能一步进入异或和为零的状态。</p>
<blockquote>
<p><strong>参考与练习</strong></p>
<ul>
<li><a href="https://oi-wiki.org/math/game-theory/impartial-game/">OI Wiki：公平组合游戏</a></li>
<li><a href="https://www.luogu.com.cn/problem/P2197">洛谷 P2197【模板】Nim 游戏</a>：标准 Nim 游戏胜负判断。</li>
<li><a href="https://codeforces.com/problemset/problem/1383/B">Codeforces 1383B — GameGame</a>：从最高有效位分析异或博弈。</li>
</ul>
</blockquote>
<hr />
<h2>十一、扩展：XOR-FWT 与异或卷积</h2>
<h3>1. XOR 卷积</h3>
<p>给定两个长度为 $n$ 的数组 $A,B$，定义：</p>
<p>$$
C_k=\sum_{i\oplus j=k}A_iB_j
$$</p>
<p>这称为 XOR 卷积。</p>
<p>直接枚举所有 $i,j$ 的时间复杂度为：</p>
<p>$$
O(n^2)
$$</p>
<p>当数组长度是 $2$ 的幂时，可以使用快速沃尔什变换，将复杂度优化为：</p>
<p>$$
O(n\log n)
$$</p>
<h3>2. XOR 蝶形</h3>
<p>对于一对数 $x,y$，XOR-FWT 的蝶形变换为：</p>
<p>$$
(x,y)\longrightarrow(x+y,x-y)
$$</p>
<p>完整模板如下：</p>
<pre><code>void fwtXor(vector&lt;long long&gt;&amp; a, bool inverse) {
    const int n = static_cast&lt;int&gt;(a.size());

    for (int len = 1; len &lt; n; len &lt;&lt;= 1) {
        for (int start = 0; start &lt; n; start += len &lt;&lt; 1) {
            for (int i = 0; i &lt; len; ++i) {
                const long long x = a[start + i];
                const long long y = a[start + i + len];

                a[start + i] = x + y;
                a[start + i + len] = x - y;
            }
        }
    }

    if (inverse) {
        for (long long&amp; x : a) {
            x /= n;
        }
    }
}
</code></pre>
<p>卷积过程可以写成：</p>
<pre><code>fwtXor(a, false);
fwtXor(b, false);

for (int i = 0; i &lt; n; ++i) {
    a[i] *= b[i];
}

fwtXor(a, true);
</code></pre>
<p>最终数组 <code>a</code> 就是 XOR 卷积结果。</p>
<p>对 XOR-FWT 连续执行两次变换，会使每个元素整体乘以 $n$，因此逆变换需要除以 $n$。</p>
<p>在普通整数运算中，应确保最终结果能够整除 $n$，并注意中间值可能超过 <code>long long</code> 的范围。</p>
<p>在模意义下，不能直接使用整数除法，而应乘以：</p>
<p>$$
n^{-1}\pmod M
$$</p>
<p>其中 $n^{-1}$ 是 $n$ 关于模数 $M$ 的乘法逆元。</p>
<blockquote>
<p><strong>参考资料</strong></p>
<ul>
<li><a href="https://oi-wiki.org/math/poly/fwt/">OI Wiki：快速沃尔什变换</a></li>
</ul>
</blockquote>
]]></content>
    <author><name>ZXHYCZX</name></author>
    <category term="算法笔记"/>
  </entry>
</feed>
