<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Zheng_iii 的博客]]></title><description><![CDATA[这是一个正经的博客]]></description><link>https://blog.z3i.com.cn</link><generator>Yohaku (https://github.com/Innei/Yohaku)</generator><lastBuildDate>Sat, 19 Sep 2026 18:22:30 GMT</lastBuildDate><atom:link href="https://blog.z3i.com.cn/feed" rel="self" type="application/rss+xml"/><pubDate>Sat, 19 Sep 2026 18:22:30 GMT</pubDate><language><![CDATA[zh-CN]]></language><item><title><![CDATA[百度 2026 初赛第一轮题解]]></title><description><![CDATA[<div><blockquote>此渲染由 Yohaku API 生成，或存排版之虞，最佳体验请往：<a href="https://blog.z3i.com.cn/posts/default/baidu20261">https://blog.z3i.com.cn/posts/default/baidu20261</a></blockquote><div><p><strong>主观难度评分：</strong></p><p>Easy：01, 05, 07</p><p>Medium：02, 04</p><p>Medium Hard：03，06</p><p>Hard：08</p><h2 id="01">01</h2><p>首先发现，药一定越靠后用越优，然后就是你在 ab 都能杀的情况下，先杀 a 和先杀 b 没区别，所以我们让血少的在前面一定不劣，所以把 $(a<em>i,b</em>i)$ 存进数组，按 $a_i$ 从小到大排序，打不动了再用药水即可。然后要注意一下最后药水可能会有剩余，全用掉即可。</p><pre class="language-cpp lang-cpp"><code class="language-cpp lang-cpp">void solve() {
    int n, k, s; in &gt;&gt; n &gt;&gt; k &gt;&gt; s;
    std::vector&lt;pii&gt; a(n+1);
    rep(i,1,n) in &gt;&gt; a[i].fi &gt;&gt; a[i].se;
    sort(a.begin()+1,a.end(),[&amp;](pii x,pii y) { return x.fi &lt; y.fi; });
    rep(i,1,n) {
        if(a[i].fi &lt;= s) { s += a[i].se; }
        else {
            while(a[i].fi &gt; s &amp;&amp; k) s *= 2, k--;
            if(a[i].fi &lt;= s) s += a[i].se;
        }
    }
    while(k) s *= 2, k--;
    out &lt;&lt; s &lt;&lt; &#x27;\n&#x27;;
}
</code></pre>
<h2 id="05">05</h2><p>用 $100$ 减去 $x$ 和 $y$ 即可。</p><pre class="language-cpp lang-cpp"><code class="language-cpp lang-cpp">void solve() {
    int x, y; in &gt;&gt; x &gt;&gt; y;
    out &lt;&lt; 100 - x - y &lt;&lt; &#x27;\n&#x27;;
}
</code></pre>
<h2 id="07">07</h2><p>先讨论一下没有 $-1$ 的情况，此时只有得票最多的人会当选，注意只能有一个，如果有多个人得票同时为最多的。那么没有人可能当选。</p><p>由于一共只有 $n$ 票，所以未知的票数是固定的，是 $n - sum$，我们枚举每个人，判断能不能让他成为最高票的即可。</p><p>具体的，若 $a<em>i = -1$ ，我们把全部的未知票都给他，判断 $n - sum$ 是否大于原序列的最大值。若 $a</em>i \ne -1$ ，因为 $a<em>i  \ne -1$ 的位置的票数已经确定，所以我们把票在 $a</em>i = -1$ 的所有位置均摊，此时均摊开的最值是 $\lceil \frac{n-sum}{\sum[a<em>i=-1]} \rceil$  ，判断当前位置是不是唯一的原序列最值，还有能不能比在 $a</em>i = -1$ 的位置均摊开的最值大即可。</p><p>注意要特判一下除以 $0$。</p><pre class="language-cpp lang-cpp"><code class="language-cpp lang-cpp">void solve() {
    int n; in &gt;&gt; n;
    vi a(n+1);
    rep(i,1,n) in &gt;&gt; a[i];
    int mx = 0;
    rep(i,1,n) ckmax(mx,a[i]);
    int mxc = 0;
    rep(i,1,n) if(a[i] == mx) mxc++;
    int s = 0;
    int cnt = n;
    rep(i,1,n) if(a[i] != -1) s += a[i], cnt--; 
    int c = n - s;

    rep(i,1,n) {
        if(a[i] == -1) {
            if(c &gt; mx) out &lt;&lt; i &lt;&lt; &#x27; &#x27;;
        } else {
            if(cnt == 0) {
                if(a[i] &gt;= mx &amp;&amp; mxc == 1) out &lt;&lt; i &lt;&lt; &#x27; &#x27;;
                continue;
            }
            if((c + (cnt-1)) / cnt &lt; a[i] &amp;&amp; a[i] &gt;= mx &amp;&amp; mxc == 1) {
                out &lt;&lt; i &lt;&lt; &#x27; &#x27;;
            }
        }
    }
}
</code></pre>
<h2 id="02">02</h2><p>首先考虑最终变成答案的那个长度为 $k$ 的区间。</p><p>设这 $k$ 个元素在原数组中的位置一共分成 $c$ 段连续区间，那么需要的最少翻转次数正好是 $c-1$。</p><p>所以 $c$ 段可以用 $c-1$ 次操作合并。</p><p>一次区间翻转只有两个端点会改变“选中 / 未选中”的相邻关系，因此一次最多让选中位置的连续段数减少 $1$，所以至少也要 $c-1$ 次。</p><p>因此原问题等价为：选择 $k$ 个元素，使它们在原数组的位置至多组成 $m+1$ 个连续段，最小化这些元素的极差。</p><p>接着考虑固定一个值域 $[L,R]$。</p><p>把满足</p><p>$$
L\le a_i\le R
$$</p><p>的位置全部标记出来，它们在原数组上会形成若干个连续段，长度为</p><p>$$
s<em>1,s</em>2,\ldots,s_c.
$$</p><p>我们最多能从 $m+1$ 个连续段中选元素，所以显然应该选最长的 $m+1$ 段。</p><p>因此当前值域可行，当且仅当：最大的 $m+1$ 个连续段长度之和 $\ge k$ 。</p><p>注意并不要求恰好把这些段全部选完。如果总长度超过 $k$，最后一个段只取一部分即可，仍然不会增加连续段数量。</p><p>于是把所有 $(a<em>i,i)$ 按 $a</em>i$ 排序，双指针维护当前值域，相同的值一起加入、一起删除。</p>
<p>类似 ODT 的，我们用 map 维护当前所有连续段：</p><ul><li><p>加入一个位置时，只可能和左右两个连续段合并；</p></li><li><p>删除一个位置时，只可能把所在连续段拆成左右两段。</p></li></ul><p>类似对顶堆的，我们再用两个 multiset 维护所有段长：</p><ul><li>左堆保存最大的 $m+1$ 个段长；</li><li>右堆保存剩余段长；</li><li><code>sum</code> 维护左堆内段长之和。</li></ul><p>那么当前值域是否合法只需要判断 <code>sum &gt;= k</code>。</p><p>每个位置只会加入、删除一次，每次修改复杂度为 $O(\log n)$。</p>
<p>使用线段树也可以维护所有段长，具体的就是我们开一个权值线段树，记录每个段长的连续段有多少个。</p><p>再维护 $sum<em>i$ 表示 $cnt</em>i \times i$ ，查询用线段树二分找第 $m+1$ 大所在位置即可，下面提供这种做法的代码。</p><pre class="language-cpp lang-cpp"><code class="language-cpp lang-cpp">struct Tag {
    int add = 0;
    void apply(const Tag&amp; t) { add += t.add; }
};

struct Info {
    int cnt = 0, sum = 0, v = 0;
    void apply(const Tag&amp; t) { cnt += t.add; sum += v * t.add; }
    friend Info operator+(const Info&amp; a, const Info&amp; b) {
        return {a.cnt + b.cnt, a.sum + b.sum, 0};
    }
};

void solve() {
    int n, m, k; in &gt;&gt; n &gt;&gt; m &gt;&gt; k;
    std::vector&lt;pii&gt; a(n);
    rep(i,0,n-1) { in &gt;&gt; a[i].fi; a[i].se = i + 1; }
    sort(all(a));
    vi val;
    std::vector&lt;vi&gt; pos;
    for(int i = 0, j; i &lt; n; i = j) {
        for(j = i; j &lt; n &amp;&amp; a[j].fi == a[i].fi; ++j);
        val.pb(a[i].fi);
        pos.pb({});
        rep(t,i,j-1) pos.back().pb(a[t].se);
    }
    std::vector&lt;Info&gt; ini(n + 1);
    rep(i,1,n) ini[i].v = i;

    SGT&lt;Info,Tag&gt; sgt(ini);
    std::map&lt;int,int&gt; seg;
    auto addl = [&amp;](int x) { sgt.upd(x, x, {1}); };
    auto dell = [&amp;](int x) { sgt.upd(x, x, {-1}); };

    auto add = [&amp;](int x) {
        auto r = seg.upper_bound(x), l = r;
        bool L = 0;
        bool R = (r != seg.end() &amp;&amp; r-&gt;fi == x + 1);
        if(r != seg.begin()) {
            --l; L = (l-&gt;se == x - 1);
        }
        if(L &amp;&amp; R) {
            int p = l-&gt;fi, q = r-&gt;se;
            dell(l-&gt;se - l-&gt;fi + 1);
            dell(r-&gt;se - r-&gt;fi + 1);
            seg.erase(r);
            l-&gt;se = q;
            addl(q - p + 1);
        } else if (L) {
            dell(l-&gt;se - l-&gt;fi + 1);
            l-&gt;se = x;
            addl(l-&gt;se - l-&gt;fi + 1);
        } else if (R) {
            int q = r-&gt;se;
            dell(r-&gt;se - r-&gt;fi + 1);
            seg.erase(r);
            seg[x] = q;
            addl(q - x + 1);
        } else {
            seg[x] = x;
            addl(1);
        }
    };

    auto del = [&amp;](int x) {
        auto it = prev(seg.upper_bound(x));
        int l = it-&gt;fi, r = it-&gt;se;
        dell(r - l + 1);
        seg.erase(it);
        if(l &lt; x) { seg[l] = x - 1; addl(x - l); }
        if(x &lt; r) { seg[x + 1] = r; addl(r - x); }
    };

    int ans = 4e18, r = 0;

    rep(l,0,(int)val.size()-1) {
        while(r &lt; val.size() &amp;&amp; sgt.topk(m + 1) &lt; k) {
            for (int x : pos[r]) add(x);
            r++;
        }
        if(sgt.topk(m + 1) &gt;= k) ckmin(ans, val[r - 1] - val[l]);
        for(int x : pos[l]) del(x);
    }

    out &lt;&lt; ans &lt;&lt; &#x27;\n&#x27;;
}
</code></pre>
<h2 id="04">04</h2><p>首先观察边权的含义。</p><p>对于一个 $A_{l,r}$，可以把它看成区间 $[l,r]$ 的权值。对于一条边 $(i,j)$，其中 $i&lt;j$，题目中的两部分分别表示：</p><ul><li>$i\in[l,r]$，但 $j\notin[l,r]$；</li><li>$j\in[l,r]$，但 $i\notin[l,r]$。</li></ul><p>所以 $e_{i,j}$ 实际上就是所有恰好包含 $i,j$ 中一个点的区间 $[l,r]$ 的权值和。</p><p>接下来证明一定存在一个最优匹配没有交叉，这个通过打表是易证的，可以跳过。</p><p>考虑两条相交的边</p><p>$$
(i,k),(j,l),\qquad i&lt;j&lt;k&lt;l
$$</p><p>对于任意一个区间 $[x,y]$，因为它在数轴上连续，所以考虑 $i,j,k,l$ 是否属于这个区间时，属于区间的点一定也是连续的一段。</p><p>于是对这个区间单独考虑，可以发现</p><p>$$
[\text{区间恰好包含 }i,k\text{ 之一}]
+
[\text{区间恰好包含 }j,l\text{ 之一}]
$$</p><p>一定不小于</p><p>$$
[\text{区间恰好包含 }i,j\text{ 之一}]
+
[\text{区间恰好包含 }k,l\text{ 之一}]
$$</p><p>又因为所有 $A_{x,y}\ge 0$，所以有</p><p>$$
e<em>{i,j}+e</em>{k,l}\le e<em>{i,k}+e</em>{j,l}
$$</p><p>也就是说，如果匹配中存在两条交叉边 $(i,k),(j,l)$，我们可以把它们改成</p><p>$$
(i,j),(k,l)
$$</p><p>而不会让答案变大。</p><p>不断消除交叉后，一定存在一组最优匹配，其中所有边都不相交。</p><p>$\mathrm{Q.E.D.}$</p><p>于是就可以做区间 DP。</p><p>令 $f_{l,r}$ 表示区间 $[l,r]$ 内所有点进行不交叉完美匹配的最小权值，只考虑长度为偶数的区间。</p><p>假设 $l$ 和 $k$ 匹配。由于匹配不能交叉，那么剩余点一定分别在</p><p>$$
[l+1,k-1]
$$</p><p>和</p><p>$$
[k+1,r]
$$</p><p>内部独立匹配。</p><p>同时 $[l+1,k-1]$ 的长度必须为偶数，所以 $k$ 每次增加 $2$。</p><p>转移就是</p><p>$$
f<em>{l,r}
=
\min</em>{\substack{l&lt;k\le r\k-l\text{ 为奇数}}}
\left(
f<em>{l+1,k-1}
+
f</em>{k+1,r}
+
e_{l,k}
\right)
$$</p><p>边权 $e_{i,j}$ 可以先对 $A$ 做二维前缀和。根据题目定义，</p><p>$$
e<em>{i,j}
=
\sum</em>{l=1}^{i}\sum<em>{r=i}^{j-1}A</em>{l,r}
+
\sum<em>{l=i+1}^{j}\sum</em>{r=j}^{n}A_{l,r},
$$</p><p>两块都是矩形，二维前缀和可以 $O(1)$ 求出，因此预处理所有边权复杂度为 $O(n^2)$。</p><p>最后的复杂度看起来是 $O(n^3)$，但由于只枚举偶数长度，并且转移时 $k$ 每次增加 $2$，实际转移次数会少很多。</p><p>实测转移次数大概在 $1.4 \times 10^8$ 次左右，是可过的。</p><pre class="language-cpp lang-cpp"><code class="language-cpp lang-cpp">const int N = 2e3+10;
int a[N][N], s[N][N], n, e[N][N], f[N][N];

void init() {
    rep(i,1,n) {
        rep(j,1,n) {
            s[i][j] = s[i-1][j] + s[i][j-1] - s[i-1][j-1] + a[i][j];
        }
    }
}

int qry(int x, int y, int xx, int yy) {
    return s[xx][yy] + s[x-1][y-1] - s[x-1][yy] - s[xx][y-1];
}

void solve() {
    in &gt;&gt; n;
    rep(i,1,n) rep(j,i,n) in &gt;&gt; a[i][j];
    init();
    rep(i,1,n) rep(j,i+1,n) {
        e[i][j] = e[j][i] =
            qry(1,i,i,j-1) + qry(i+1,j,j,n);
    }
    for(int len = 2;len &lt;= n;len += 2) {
        rep(l,1,n-len+1) {
            int r = l + len - 1;
            f[l][r] = inf;
            for(int k = l + 1;k &lt;= r;k += 2) {
                f[l][r] = std::min(f[l][r],f[l+1][k-1] + f[k+1][r] + e[l][k]);
            }
        }
    }
    out &lt;&lt; f[1][n] &lt;&lt; &#x27;\n&#x27;;
}
</code></pre>
<h2 id="03">03</h2><p>首先考虑枚举 $T_1$ 上一条链的两个端点。</p><p>固定一个端点 $r$，枚举另一个端点 $x$，集合就是 $T_1$ 上的链 $P(r,x)$。</p><p>因为 $T_2[P(r,x)]$ 一定是一片森林，根据点减边容斥，它连通当且仅当</p><p>$$</p><p>|V|-|E|=1.</p><p>$$</p><p>于是定义</p><p>$$
f<em>r(x)=|P(r,x)|-|E(T</em>2[P(r,x)])|.
$$</p><p>这个值就是 $T<em>2[P(r,x)]$ 的连通块个数，所以始终有 $f</em>r(x)\ge1$，并且链合法当且仅当 $f_r(x)=1$。</p><p>先把 $T_1$ 以当前点 $r$ 为根。</p><p>对于一个点 $u$，它出现在链 $P(r,x)$ 上，当且仅当 $x$ 在 $u$ 的子树中。</p><p>所以每个点 $u$ 会对其子树内所有点贡献 $+1$，也就是</p><p>$$
\operatorname{subtree}(u)\mathrel{+}=1.
$$</p><p>再考虑 $T_2$ 中一条边 $(u,v)$。</p><p>它会被算进 $E(T_2[P(r,x)])$，当且仅当 $u,v$ 都在链 $P(r,x)$ 上。</p><p>这要求 $u,v$ 在当前根下存在祖先关系。假设 $u$ 是 $v$ 的祖先，那么只要 $x\in\operatorname{subtree}(v)$，链 $P(r,x)$ 就会同时经过 $u,v$。</p><p>因此这条边相当于</p><p>$$
\operatorname{subtree}(v)\mathrel{-}=1.
$$</p><p>所以固定根以后，所有 $f_r(x)$ 都可以通过若干次子树加得到。</p><p>而子树在 DFS 序上是连续区间，因此可以用线段树维护。</p><p>线段树维护：</p><ul><li><code>mn</code>：区间最小值；</li><li><code>cnt</code>：最小值出现次数；</li><li><code>tag</code>：区间加。</li></ul><p>因为 $f<em>r(x)\ge1$，并且 $f</em>r(r)=1$，所以全局最小值一定为 $1$。于是线段树根节点的 <code>cnt</code> 就是当前以 $r$ 为一个端点的合法链数量。</p><p>考虑换根，先只看点的贡献。</p><p>对于 $x\in\operatorname{subtree}(v)$，链 $P(v,x)$ 比 $P(u,x)$ 少了一个点 $u$，所以值减 $1$。</p><p>对于 $x\notin\operatorname{subtree}(v)$，链 $P(v,x)$ 比原来多了一个点 $v$，所以值加 $1$。</p><p>因此可以统一写成</p><p>$$
[1,n]\mathrel{+}=1,\qquad
\operatorname{subtree}(v)\mathrel{-}=2.
$$</p><p>考虑 $T_2$ 中一条边 $(a,b)$。</p><p>设 $p$ 是 $T_1$ 上从 $a$ 往 $b$ 走的第一个点，$q$ 是从 $b$ 往 $a$ 走的第一个点。</p><p>随着根在 $T_1$ 上移动，$a,b$ 是否存在祖先关系，只会在根跨过</p><p>$$
(a,p),\qquad (b,q)
$$</p><p>这两条边时发生变化。</p><p>因此每条 $T_2$ 边只需要挂 $O(1)$ 个换根事件。</p><p>跨过对应边时，对另一端所在的那一侧整体加上 $+1$ 或 $-1$。</p><p>而删掉 $T_1$ 中一条边后的一侧，要么是一个子树，要么是一个子树的补集，所以都可以用 DFS 序上的 $O(1)$ 个区间表示。</p><p>于是 DFS 换根时只需要：</p><pre class="language-text lang-text"><code class="language-text lang-text">u -&gt; v

全局 +1
subtree(v) -2
执行挂在边 (u,v) 上的所有事件

统计当前线段树 cnt

DFS(v)

撤销修改
</code></pre>
<p>每条 $T_2$ 边只会产生常数个事件，所以总修改次数为 $O(n)$。</p><p>设所有根下线段树得到的 <code>cnt</code> 之和为 $C$。</p><p>单点集合 ${u}$ 只会计算一次，一共有 $n$ 个，其余路径都会被计算两次。</p><p>因此最终答案为</p><p>$$
n+\frac{C-n}{2}
=
\frac{C+n}{2}.
$$</p><pre class="language-cpp lang-cpp"><code class="language-cpp lang-cpp">struct Tag {
    int add = 0;
    void apply(const Tag &amp;t) { add += t.add; }
};
struct Info {
    int mn = 0, cnt = 0;
    void apply(const Tag &amp;t) { mn += t.add; }
    friend Info operator+(const Info &amp;a, const Info &amp;b) {
        if (a.mn &lt; b.mn) return a;
        if (b.mn &lt; a.mn) return b;
        return {a.mn, a.cnt + b.cnt};
    }
};

const int N = 1e6 + 10;

int n, tim, sum;
int fa[N], dep[N], siz[N], son[N], top[N], dfn[N], rk[N], dif[N];
vi E[N];
std::vector&lt;pii&gt; ev[N];

SGT&lt;Info,Tag&gt; *tr;

void dfs1(int u, int f) {
    fa[u] = f;
    dep[u] = dep[f] + 1;
    siz[u] = 1;
    for(int v : E[u]) if(v != f) {
        dfs1(v, u);
        siz[u] += siz[v];
        if(!son[u] || siz[v] &gt; siz[son[u]]) son[u] = v;
    }
}

void dfs2(int u, int tp) {
    top[u] = tp;
    dfn[u] = ++tim;
    rk[tim] = u;
    if(son[u]) dfs2(son[u], tp);
    for(int v : E[u]) {
        if(v == fa[u] || v == son[u]) continue;
        dfs2(v, v);
    }
}

bool anc(int u, int v) {
    return dfn[u] &lt;= dfn[v] &amp;&amp; dfn[v] &lt; dfn[u] + siz[u];
}

int child(int u, int v) {
    while(top[u] != top[v]) {
        if(fa[top[v]] == u) return top[v];
        v = fa[top[v]];
    }
    return rk[dfn[u] + 1];
}

pii side(int u, int v) {
    if(!anc(u, v)) return {u, u};
    int c = child(u, v);
    return {c, -c};
}

void add_sub(int u, int w) { tr-&gt;upd(dfn[u], dfn[u] + siz[u] - 1, {w}); }

void add_side(int x, int w) {
    if(x &gt; 0) add_sub(x, w);
    else { tr-&gt;upd(1, n, {w}); add_sub(-x, -w); }
}

void mvrt(int v, int op) {
    tr-&gt;upd(1, n, {op});
    add_sub(v, -2 * op);
    for(auto [x, w] : ev[v]) add_side(x, w * op);
}

void dfs3(int u) {
    sum += tr-&gt;info[1].cnt;
    for(int v : E[u]) if(fa[v] == u) { mvrt(v, 1); dfs3(v); mvrt(v, -1); }
}

void init() {
    rep(i,1,n) { E[i].clear(); ev[i].clear(); son[i] = dif[i] = 0; }
}

void solve() {
    in &gt;&gt; n;
    init();
    dif[n + 1] = 0;
    tim = 0;
    rep(i,2,n) {
        int u, v; in &gt;&gt; u &gt;&gt; v;
        E[u].eb(v); E[v].eb(u);
    }
    dfs1(1, 0);
    dfs2(1, 1);
    rep(i,2,n) {
        int a, b;  in &gt;&gt; a &gt;&gt; b;
        if(anc(a, b)) {
            dif[dfn[b]]--;
            dif[dfn[b] + siz[b]]++;
        } else if (anc(b, a)) {
            dif[dfn[a]]--;
            dif[dfn[a] + siz[a]]++;
        }
        auto [ca, A] = side(a, b);
        auto [cb, B] = side(b, a);
        ev[ca].eb(B, A &gt; 0 ? -1 : 1);
        ev[cb].eb(A, B &gt; 0 ? -1 : 1);
    }
    rep(i,1,n) dif[i] += dif[i - 1];
    std::vector&lt;Info&gt; init(n + 1);
    rep(i,1,n) init[i] = {dep[rk[i]] + dif[i], 1};
    SGT&lt;Info,Tag&gt; tmp(init); tr = &amp;tmp;
    sum = 0;
    dfs3(1);
    out &lt;&lt; (sum + n) / 2 &lt;&lt; &#x27;\n&#x27;;
}
</code></pre>
<h2 id="06">06</h2><p>这是若干个互不影响的公平组合游戏，所以考虑求每台游戏机的 SG 值，最后做 nim-sum。</p><p>但这题和普通 SG 有一点不同：操作 2、3 中的 $x$ 可以任意大，因此一个状态可能有无限多个后继。比如某个状态能一步走到 SG 值 $ 0,1,2,3,\ldots $，那么它的 mex 就不是一个有限整数了，而是 $\omega$。</p><p>这里 $\omega$ 表示第一个无限序数。之后还有 $\omega+1,\omega+2,\ldots,\omega\cdot2,\omega\cdot2+1,\ldots$。</p><p>好在本题所有 SG 值都只会形如 $ \omega p+q $，其中 $p,q$ 都是有限非负整数，所以直接用二元组 $(p,q)$ 表示即可。</p><p>对于这种形式，nim-sum 满足</p><p>$$
(\omega p+q)\oplus(\omega x+y)
=
\omega(p\oplus x)+(q\oplus y)
$$</p><p>所以最终只需要分别异或 $p,q$。</p><p>我们考虑求单机的 SG 值，记 $ g(a,b,c) $ 表示状态 $(a,b,c)$ 的 SG 值。</p><p>先看 $c=0$。此时只能执行操作 1，也就是把 $a$ 变成任意更小的非负整数，所以就是普通 nim，有 $g(a,b,0)=a$，也就是 $g(a,b,0)=(0,a)$。</p><p>接下来考虑 $c&gt;0$。</p><p>操作 2、3 之后都会变成 $a=b=x$ 的状态，所以先观察这些状态。可以归纳得到 $g(x,x,2k+1)=\omega(k+1)$，以及 $g(x,x,2k)=\omega k+(x+1)\ (k\ge1)$。</p><p>当 $c=1$ 时，操作 2 可以令 $c$ 变为 $0$，同时选择任意 $x\ge b$。而 $g(x,x,0)=x$，所以操作 2 可以到达 $ b,b+1,b+2,\ldots $。</p><p>再考虑操作 1。如果 $a&lt;b$，前面的状态依次填掉 $0,1,\ldots,a-1$，所以 $g(a,b,1)=a$。</p><p>如果 $a=b$，所有有限非负整数都已经成为后继，因此 $g(b,b,1)=\omega$。之后继续增大 $a$，依次得到 $ \omega+1,\omega+2,\ldots $。</p><p>所以</p><p>$$
g(a,b,1)=
\begin{cases}
a,&amp;a&lt;b,\
\omega+(a-b),&amp;a\ge b
\end{cases}
$$</p><p>当 $c=2k$，其中 $k\ge1$ 时，利用前面的归纳结论，操作 3 可以到达 $c\le2k-2$ 的所有对角状态，这些 SG 值能够覆盖 $[0,\omega k)$。</p><p>操作 2 还可以令新的 $c=2k-1$，而 $g(x,x,2k-1)=\omega k$，所以后继已经覆盖 $[0,\omega k]$。</p><p>因此当前 mex 从 $\omega k+1$ 开始。再通过操作 1，随着 $a$ 增大依次往后得到，所以</p><p>$$
g(a,b,2k)=\omega k+(a+1)
$$</p><p>注意这里与 $b$ 无关。</p><p>当 $c=2k+1$ 时，先考虑 $k\ge1$。</p><p>操作 3 可以覆盖 $[0,\omega k]$，而操作 2 取新的 $c=2k$ 时，有 $g(x,x,2k)=\omega k+(x+1)$。又要求 $x\ge b$，所以可以覆盖 $\omega k+(b+1),\omega k+(b+2),\ldots$。</p><p>因此这一层唯一缺少的是 $\omega k+1,\omega k+2,\ldots,\omega k+b$。</p><p>操作 1 会随着 $a$ 增大依次填掉这些空缺。所以当 $a&lt;b$ 时，有 $g(a,b,2k+1)=\omega k+(a+1)$。</p><p>当 $a=b$ 时，这一层所有值都被覆盖，于是 mex 跳到 $\omega(k+1)$。之后继续随 $a$ 增大，因此</p><p>$$
g(a,b,2k+1)=
\begin{cases}
\omega k+(a+1),&amp;a&lt;b,\
\omega(k+1)+(a-b),&amp;a\ge b
\end{cases}
$$</p><p>把 SG 值 $ \omega p+q $ 记为二元组 $(p,q)$，那么最终有</p><p>$$
g(a,b,c)=
\begin{cases}
(0,a),&amp;c=0,\[4pt]
\left(\frac c2,a+1\right),&amp;c&gt;0,\ c\text{ 为偶数},\[6pt]
\left(\frac{c-1}{2},a+1\right),&amp;c&gt;1,\ c\text{ 为奇数且 }a&lt;b,\[6pt]
(0,a),&amp;c=1,\ a&lt;b,\[4pt]
\left(\frac{c+1}{2},a-b\right),&amp;c\text{ 为奇数且 }a\ge b
\end{cases}
$$</p><p>最后根据 SG 定理，把所有游戏机的 SG 做 nim-sum。</p><p>由于：</p><p>$$
(\omega p+q)\oplus(\omega x+y)
=
\omega(p\oplus x)+(q\oplus y)
$$</p><p>所以分别维护两个异或和即可。</p><p>如果最后两个异或和都为 $0$，则先手必败，否则先手必胜。</p><pre class="language-cpp lang-cpp"><code class="language-cpp lang-cpp">void solve() {
    int n; in &gt;&gt; n;
    int x = 0, y = 0;
    rep(i,1,n) {
        int a, b, c;
        in &gt;&gt; a &gt;&gt; b &gt;&gt; c;
        pii g;
        if(c == 0) {
            g = {0, a};
        } else if(c % 2 == 0) {
            g = {c / 2, a + 1};
        } else if(a &gt;= b) {
            g = {(c + 1) / 2, a - b};
        } else if(c == 1) {
            g = {0, a};
        } else {
            g = {c / 2, a + 1};
        }
        x ^= g.fi;
        y ^= g.se;
    }
    out &lt;&lt; (x || y ? &quot;First&quot; : &quot;Second&quot;) &lt;&lt; &#x27;\n&#x27;;
}
</code></pre>
<h2 id="08">08</h2><p>设一棵以 $1$ 为根的树中，点 $i$ 的儿子数为 $s_i$，树的权值为</p><p>$$
\prod<em>i w</em>{s_i}
$$</p><p>要求点集 ${1,2,\ldots,k}$ 的诱导子图连通。</p><p>有结论：对于一棵 $m$ 个点、以 $r$ 为根的有标号树，令 $s_i$ 为点 $i$ 的儿子数，则</p><p>$$
\sum<em>T \prod</em>i x<em>i^{s</em>i}
=
x<em>r\left(\sum</em>i x_i\right)^{m-2}
$$</p><p>原因是 Prufer 序列中，点 $i$ 出现次数为 $\deg_i-1$。</p><p>对于非根节点有 $\deg<em>i=s</em>i+1$，所以出现 $s<em>i$ 次；对于根节点有 $\deg</em>r=s<em>r$，所以只出现 $s</em>r-1$ 次，因此需要额外乘一个 $x_r$。</p>
<p>记</p><p>$$
A=x<em>1+x</em>2+\cdots+x<em>k,\qquad
B=x</em>1+x<em>2+\cdots+x</em>n
$$</p><p>因为前 $k$ 个点的诱导子图连通，而整张图是一棵树，所以前 $k$ 个点内部本身恰好是一棵树。</p><p>先只看前 $k$ 个点，它们以 $1$ 为根，贡献为</p><p>$$
x_1A^{k-2}
$$</p><p>接着把这 $k$ 个点整体缩成一个超级点。</p><p>缩点后有 $n-k+1$ 个点，并以超级点为根。若某个点接到超级点上，那么在原树中，它可以选择前 $k$ 个点中的任意一个作为父亲，因此超级点对应的变量不是一个单独的 $x$，而是 $A$。</p><p>所以缩点后的贡献为</p><p>$$
A B^{n-k-1}
$$</p><p>于是当 $k&lt;n$ 时，所有好树的儿子数生成多项式就是</p><p>$$
x_1A^{k-1}B^{n-k-1}
$$</p><p>当 $k=n$ 时没有缩点后的部分，生成多项式为</p><p>$$
x_1A^{n-2}
$$</p><p>定义 EGF</p><p>$$
W(x)=\sum<em>{i=0}^{n-1}\frac{w</em>i}{i!}x^i
$$</p><p>考虑 $(x<em>1+\cdots+x</em>m)^d$。</p><p>若每个变量次数分别为 $c<em>1,\ldots,c</em>m$，那么对应系数为</p><p>$$
\frac{d!}{c<em>1!c</em>2!\cdots c_m!}
$$</p><p>因此把 $x<em>i^{c</em>i}$ 的权值替换成 $w<em>{c</em>i}$ 后，总和就是</p><p>$$
d![x^d]W(x)^m
$$</p><p>根节点 $1$ 比较特殊，因为前面额外有一个 $x_1$。</p><p>如果它在剩余多项式中的次数为 $c$，那么实际儿子数为 $c+1$，因此根节点对应</p><p>$$
W&#x27;(x)
=
\sum<em>{c\ge0}\frac{w</em>{c+1}}{c!}x^c
$$</p><p>先考虑 $k&lt;n$。</p><p>令</p><p>$$
q=n-k-1
$$</p><p>因为 $B=A+C$，其中 $C=x<em>{k+1}+\cdots+x</em>n$，所以</p><p>$$
x<em>1A^{k-1}B^q
=
\sum</em>{j=0}^{q}
\binom qj
x_1A^{k-1+j}C^{q-j}
$$</p><p>定义</p><p>$$
F(x)=W(x)^k,\qquad
G(x)=W(x)^{n-k}
$$</p><p>对于固定的 $j$，前 $k$ 个点贡献为</p><p>$$
(k-1+j)!
[x^{k-1+j}]
W&#x27;(x)W(x)^{k-1}
$$</p><p>而</p><p>$$
W&#x27;(x)W(x)^{k-1}
=
\frac{1}{k}F&#x27;(x)
$$</p><p>所以这一部分等于</p><p>$$
\frac{(k+j)!}{k}F_{k+j}
$$</p><p>其中 $F_i=[x^i]F(x)$。</p><p>后 $n-k$ 个点贡献为</p><p>$$
(q-j)!G_{q-j}
$$</p><p>再乘上 $\binom qj$，得到</p><p>$$
\text{Ans}
=
\frac{q!}{k}
\sum<em>{j=0}^{q}
\frac{(k+j)!}{j!}
F</em>{k+j}G_{q-j}
$$</p><p>当 $k=n$ 时，</p><p>$$
\text{Ans}
=
\frac{(n-1)!}{n}
[x^{n-1}]W(x)^n
$$</p><p>所以只需要求出 $W^k$ 和 $W^{n-k}$ 的前 $n$ 项，这个用 $O(nlogn)$ 的多项式幂截断即可。</p><pre class="language-cpp lang-cpp"><code class="language-cpp lang-cpp">void solve() {
    int n, k; in &gt;&gt; n &gt;&gt; k;
    std::vector&lt;Z&gt; a(n);
    rep(i,0,n-1) {
        int w; in &gt;&gt; w;
        a[i] = Z(w) * ifac[i];
    }
    poly W(a);
    poly F = W.pow(k, n);
    Z ans = 0;
    if(k == n) {
        ans = fac[n - 1] / Z(n) * F[n - 1];
    } else {
        poly G = W.pow(n - k, n);
        int q = n - k - 1;
        rep(j,0,q) {
            ans += fac[k + j] * ifac[j] * F[k + j] * G[q - j];
        }
        ans *= fac[q] / Z(k);
    }
    out &lt;&lt; ans.v &lt;&lt; &#x27;\n&#x27;;
}
</code></pre></div><p style="text-align:right"><a href="https://blog.z3i.com.cn/posts/default/baidu20261#comments">览毕，何不一言？</a></p></div>]]></description><link>https://blog.z3i.com.cn/posts/default/baidu20261</link><guid isPermaLink="true">https://blog.z3i.com.cn/posts/default/baidu20261</guid><dc:creator><![CDATA[Zheng_iii]]></dc:creator><pubDate>Tue, 01 Sep 2026 20:20:37 GMT</pubDate></item><item><title><![CDATA[使用矩阵和张量积构造 FWT]]></title><description><![CDATA[<div><blockquote>此渲染由 Yohaku API 生成，或存排版之虞，最佳体验请往：<a href="https://blog.z3i.com.cn/notes/1">https://blog.z3i.com.cn/notes/1</a></blockquote><div><p>对于位运算卷积</p><p>$$
C<em>k=\sum</em>{i\circ j=k}A<em>iB</em>j,
$$</p><p>其中 $\circ$ 可以是 OR、AND 或 XOR。我们希望构造一个线性变换 $F$，使卷积在变换后变成逐点乘法：</p><p>$$
F(C)<em>i=F(A)</em>iF(B)_i.
$$</p><p>于是可以按照下面的流程计算卷积：</p><ol start="1"><li>分别对 $A,B$ 做 FWT；</li><li>将变换后的数组逐点相乘；</li><li>对结果做逆 FWT，还原出 $C$。</li></ol><p>下面分别推导 OR、AND 和 XOR 卷积对应的变换。</p><h2 id="or-">OR 卷积</h2><h3 id="1-">1. 推导矩阵需要满足的条件</h3><p>OR 卷积定义为</p><p>$$
C<em>k=\sum</em>{x\lor y=k}A<em>xB</em>y.
$$</p><p>设线性变换</p><p>$$
F(A)=MA,
$$</p><p>其中矩阵 $M$ 的第 $i$ 行、第 $k$ 列为 $m_{i,k}$，则</p><p>$$
F(C)<em>i=\sum</em>km<em>{i,k}C</em>k.
$$</p><p>代入 $C_k$ 的定义：</p><p>$$
\begin{aligned}
F(C)<em>i
&amp;=\sum</em>km<em>{i,k}\left(\sum</em>{x\lor y=k}A<em>xB</em>y\right)\
&amp;=\sum<em>x\sum</em>y m<em>{i,x\lor y}A</em>xB_y.
\end{aligned}
$$</p><p>因此，左边多项式中 $A<em>xB</em>y$ 的系数为</p><p>$$
m_{i,x\lor y}.
$$</p><p>另一方面，</p><p>$$
\begin{aligned}
F(A)<em>iF(B)</em>i
&amp;=\left(\sum<em>xm</em>{i,x}A<em>x\right)
  \left(\sum</em>ym<em>{i,y}B</em>y\right)\
&amp;=\sum<em>x\sum</em>y m<em>{i,x}m</em>{i,y}A<em>xB</em>y.
\end{aligned}
$$</p><p>右边多项式中 $A<em>xB</em>y$ 的系数为</p><p>$$
m<em>{i,x}m</em>{i,y}.
$$</p><p>为了使</p><p>$$
F(C)<em>i=F(A)</em>iF(B)_i
$$</p><p>对任意数组 $A,B$ 恒成立，对应项的系数必须相等。因此矩阵需要满足</p><p>$$
\boxed{m<em>{i,x\lor y}=m</em>{i,x}m_{i,y}}.
$$</p><h3 id="2-">2. 求一位二进制下的变换矩阵</h3><p>先只考虑一位二进制，即数组长度为 $2$，下标只有 $0,1$。设</p><p>$$
M<em>1=
\begin{bmatrix}
m</em>{0,0}&amp;m<em>{0,1}\
m</em>{1,0}&amp;m_{1,1}
\end{bmatrix}.
$$</p><p>对于任意一行 $i$：</p><p>当 $x=0,y=0$ 时，</p><p>$$
m<em>{i,0\lor0}=m</em>{i,0}^2,
$$</p><p>即</p><p>$$
m<em>{i,0}=m</em>{i,0}^2.
$$</p><p>在通常使用的数域或模质数域中，解为 $m_{i,0}=0$ 或 $1$。</p><p>如果 $m_{i,0}=0$，那么对任意 $x$ 都有</p><p>$$
m<em>{i,x}=m</em>{i,0\lor x}=m<em>{i,0}m</em>{i,x}=0,
$$</p><p>这一整行都会变成零，矩阵不可能可逆。因此必须取</p><p>$$
m_{i,0}=1.
$$</p><p>当 $x=1,y=1$ 时，</p><p>$$
m<em>{i,1\lor1}=m</em>{i,1}^2,
$$</p><p>即</p><p>$$
m<em>{i,1}=m</em>{i,1}^2,
$$</p><p>所以 $m_{i,1}=0$ 或 $1$。</p><p>因此每一行只能是 $[1,0]$ 或 $[1,1]$。为了使矩阵可逆，取这两个不同的行：</p><p>$$
\boxed{
M_1=
\begin{bmatrix}
1&amp;0\
1&amp;1
\end{bmatrix}
}.
$$</p><h3 id="3-">3. 从一位推广到多位</h3><p>由于不同二进制位之间互相独立，$n$ 位变换矩阵就是 $M_1$ 的 $n$ 次张量积：</p><p>$$
M<em>n=M</em>1^{\otimes n}.
$$</p><p>提取最高位，有</p><p>$$
\begin{aligned}
M<em>n
&amp;=M</em>1\otimes M<em>{n-1}\
&amp;=
\begin{bmatrix}
1&amp;0\
1&amp;1
\end{bmatrix}
\otimes M</em>{n-1}\
&amp;=
\begin{bmatrix}
M<em>{n-1}&amp;0\
M</em>{n-1}&amp;M_{n-1}
\end{bmatrix}.
\end{aligned}
$$</p><p>将长度为 $2^n$ 的数组按照最高位分成前后两半：</p><p>$$
A=
\begin{bmatrix}
A<em>0\
A</em>1
\end{bmatrix}.
$$</p><p>于是</p><p>$$
\begin{aligned}
F(A)
&amp;=M<em>nA\
&amp;=
\begin{bmatrix}
M</em>{n-1}&amp;0\
M<em>{n-1}&amp;M</em>{n-1}
\end{bmatrix}
\begin{bmatrix}
A<em>0\
A</em>1
\end{bmatrix}\
&amp;=
\begin{bmatrix}
M<em>{n-1}A</em>0\
M<em>{n-1}A</em>0+M<em>{n-1}A</em>1
\end{bmatrix}\
&amp;=
\begin{bmatrix}
F(A<em>0)\
F(A</em>0)+F(A_1)
\end{bmatrix}.
\end{aligned}
$$</p><p>因此每一级的蝶形操作为</p><p>$$
\boxed{(x,y)\longmapsto(x,x+y)}.
$$</p><p>设数组长度为 $N=2^n$，递归式为</p><p>$$
T(N)=2T\left(\frac N2\right)+O(N)=O(N\log N)=O(n2^n).
$$</p><h3 id="4-or-">4. OR 逆变换</h3><p>一位矩阵的逆为</p><p>$$
M_1^{-1}=
\begin{bmatrix}
1&amp;0\
-1&amp;1
\end{bmatrix}.
$$</p><p>又因为张量积的逆等于各矩阵逆的张量积，所以</p><p>$$
M<em>n^{-1}=
\begin{bmatrix}
M</em>{n-1}^{-1}&amp;0\
-M<em>{n-1}^{-1}&amp;M</em>{n-1}^{-1}
\end{bmatrix}.
$$</p><p>对应的逆蝶形操作为</p><p>$$
\boxed{(x,y)\longmapsto(x,y-x)}.
$$</p><p>递归实现：</p><pre class="language-cpp lang-cpp"><code class="language-cpp lang-cpp">void OR(int l, int r, int opt)
{
    if(l == r) return;
    int mid = (l + r) &gt;&gt; 1;
    OR(l, mid, opt);
    OR(mid + 1, r, opt);
    int len = mid - l + 1;
    rep(i, l, mid)
    {
        if(opt == 1) a[i + len] += a[i];
        else a[i + len] -= a[i];
    }
}
</code></pre>
<p>非递归实现：</p><pre class="language-cpp lang-cpp"><code class="language-cpp lang-cpp">void OR(Z *f, int opt)
{
    for(int i = 1; i &lt; lim; i &lt;&lt;= 1)
        for(int j = 0; j &lt; lim; j += i &lt;&lt; 1)
            rep(k, 0, i - 1)
                if(opt == 1) f[j + i + k] += f[j + k];
                else f[j + i + k] -= f[j + k];
}
</code></pre>
<h2 id="and-">AND 卷积</h2><p>AND 卷积定义为</p><p>$$
C<em>k=\sum</em>{x\land y=k}A<em>xB</em>y.
$$</p><p>完全同理，矩阵需要满足</p><p>$$
\boxed{m<em>{i,x\land y}=m</em>{i,x}m_{i,y}}.
$$</p><h3 id="1-">1. 一位变换矩阵</h3><p>AND 的单位元是 $1$。对于非零的一行，必须有 $m<em>{i,1}=1$；而 $m</em>{i,0}$ 可以取 $0$ 或 $1$。因此两种不同的行是</p><p>$$
[1,1],\qquad[0,1].
$$</p><p>取它们组成可逆矩阵：</p><p>$$
\boxed{
M_1=
\begin{bmatrix}
1&amp;1\
0&amp;1
\end{bmatrix}
}.
$$</p><h3 id="2-">2. 推广到多位</h3><p>$$
\begin{aligned}
M<em>n
&amp;=M</em>1\otimes M<em>{n-1}\
&amp;=
\begin{bmatrix}
M</em>{n-1}&amp;M<em>{n-1}\
0&amp;M</em>{n-1}
\end{bmatrix}.
\end{aligned}
$$</p><p>于是</p><p>$$
\begin{aligned}
F(A)
&amp;=
\begin{bmatrix}
M<em>{n-1}&amp;M</em>{n-1}\
0&amp;M<em>{n-1}
\end{bmatrix}
\begin{bmatrix}
A</em>0\
A<em>1
\end{bmatrix}\
&amp;=
\begin{bmatrix}
F(A</em>0)+F(A<em>1)\
F(A</em>1)
\end{bmatrix}.
\end{aligned}
$$</p><p>因此 AND 变换的蝶形操作为</p><p>$$
\boxed{(x,y)\longmapsto(x+y,y)}.
$$</p><h3 id="3-and-">3. AND 逆变换</h3><p>$$
M_1^{-1}=
\begin{bmatrix}
1&amp;-1\
0&amp;1
\end{bmatrix}.
$$</p><p>因此逆蝶形操作为</p><p>$$
\boxed{(x,y)\longmapsto(x-y,y)}.
$$</p><p>实现：</p><pre class="language-cpp lang-cpp"><code class="language-cpp lang-cpp">void AND(Z *f, int opt)
{
    for(int i = 1; i &lt; lim; i &lt;&lt;= 1)
        for(int j = 0; j &lt; lim; j += i &lt;&lt; 1)
            rep(k, 0, i - 1)
                if(opt == 1) f[j + k] += f[j + i + k];
                else f[j + k] -= f[j + i + k];
}
</code></pre>
<h2 id="xor-">XOR 卷积</h2><p>XOR 卷积定义为</p><p>$$
C<em>k=\sum</em>{x\oplus y=k}A<em>xB</em>y.
$$</p><p>矩阵需要满足</p><p>$$
\boxed{m<em>{i,x\oplus y}=m</em>{i,x}m_{i,y}}.
$$</p><h3 id="1-">1. 一位变换矩阵</h3><p>当 $x=0,y=0$ 时，</p><p>$$
m<em>{i,0}=m</em>{i,0}^2.
$$</p><p>为了避免整行退化为零，取</p><p>$$
m_{i,0}=1.
$$</p><p>当 $x=1,y=1$ 时，</p><p>$$
m<em>{i,1\oplus1}=m</em>{i,1}^2,
$$</p><p>所以</p><p>$$
m<em>{i,0}=m</em>{i,1}^2.
$$</p><p>由于 $m_{i,0}=1$，有</p><p>$$
m_{i,1}=1\quad\text{或}\quad-1.
$$</p><p>取两种不同的行组成矩阵：</p><p>$$
\boxed{
M_1=
\begin{bmatrix}
1&amp;1\
1&amp;-1
\end{bmatrix}
}.
$$</p><h3 id="2-">2. 推广到多位</h3><p>$$
\begin{aligned}
M<em>n
&amp;=M</em>1\otimes M<em>{n-1}\
&amp;=
\begin{bmatrix}
M</em>{n-1}&amp;M<em>{n-1}\
M</em>{n-1}&amp;-M_{n-1}
\end{bmatrix}.
\end{aligned}
$$</p><p>于是</p><p>$$
\begin{aligned}
F(A)
&amp;=
\begin{bmatrix}
M<em>{n-1}&amp;M</em>{n-1}\
M<em>{n-1}&amp;-M</em>{n-1}
\end{bmatrix}
\begin{bmatrix}
A<em>0\
A</em>1
\end{bmatrix}\
&amp;=
\begin{bmatrix}
F(A<em>0)+F(A</em>1)\
F(A<em>0)-F(A</em>1)
\end{bmatrix}.
\end{aligned}
$$</p><p>因此 XOR 变换的蝶形操作为</p><p>$$
\boxed{(x,y)\longmapsto(x+y,x-y)}.
$$</p><h3 id="3-xor-">3. XOR 逆变换</h3><p>一位矩阵的逆为</p><p>$$
M_1^{-1}=
\frac12
\begin{bmatrix}
1&amp;1\
1&amp;-1
\end{bmatrix}.
$$</p><p>因此逆蝶形操作为</p><p>$$
\boxed{(x,y)\longmapsto\left(\frac{x+y}{2},\frac{x-y}{2}\right)}.
$$</p><p>每一层都除以 $2$，总共经过 $n$ 层，所以整个逆变换等价于最后统一乘上 $2^{-n}$。</p><p>如果在模意义下计算，需要保证 $2$ 存在乘法逆元；例如模数为奇质数时，可以预处理</p><p>$$
i2=2^{-1}.
$$</p><p>实现：</p><pre class="language-cpp lang-cpp"><code class="language-cpp lang-cpp">void XOR(Z *f, int opt)
{
    for(int i = 1; i &lt; lim; i &lt;&lt;= 1)
        for(int j = 0; j &lt; lim; j += i &lt;&lt; 1)
            rep(k, 0, i - 1)
            {
                Z x = f[j + k], y = f[j + i + k];
                f[j + k] = x + y;
                f[j + i + k] = x - y;
                if(opt == -1)
                {
                    f[j + k] *= i2;
                    f[j + i + k] *= i2;
                }
            }
}
</code></pre>
<h2 id="">总结</h2><p>设当前处理的一对数为 $(x,y)$：</p><table><thead><tr><th> 卷积 </th><th> 正变换                  </th><th> 逆变换                                                 </th></tr></thead><tbody><tr><td> OR   </td><td> $(x,y)\mapsto(x,x+y)$   </td><td> $(x,y)\mapsto(x,y-x)$                                  </td></tr><tr><td> AND  </td><td> $(x,y)\mapsto(x+y,y)$   </td><td> $(x,y)\mapsto(x-y,y)$                                  </td></tr><tr><td> XOR  </td><td> $(x,y)\mapsto(x+y,x-y)$ </td><td> $(x,y)\mapsto\left(\frac{x+y}{2},\frac{x-y}{2}\right)$ </td></tr></tbody></table><p>三种 FWT 的时间复杂度均为</p><p>$$
\boxed{O(n2^n)=O(N\log N)},\qquad N=2^n.
$$</p></div><p style="text-align:right"><a href="https://blog.z3i.com.cn/notes/1#comments">览毕，何不一言？</a></p></div>]]></description><link>https://blog.z3i.com.cn/notes/1</link><guid isPermaLink="true">https://blog.z3i.com.cn/notes/1</guid><dc:creator><![CDATA[Zheng_iii]]></dc:creator><pubDate>Sun, 19 Jul 2026 11:12:19 GMT</pubDate></item></channel></rss>