2026.3.2 NOI 模拟赛 题解

T1 XOR and Min-cut

题意

一张 \(n\) 点的图,给定 \(s,t\)\(m\) 次操作每次加入一条无向边,每次操作后求出 \(\min_{S\subset V,s\in S,t\notin S} \bigoplus_{(u,v,w)\in E,u\in S,v\notin S} w\),多测 \(\sum n,\sum m\le10^6\)\(w\le2^{60}\),可能有重边,不存在自环

分析

对于固定的边集,先令 \(S=\{s\}\),此时权值为 \(\bigoplus_{(s,v,w)\in E}\)

更改一个点的状态(在 \(S\) 中或不在 \(S\) 中),则权值异或上其所有邻点的边权

\(W_u=\bigoplus_{(u,v,w)\in E} w\),则答案为 \(\min_{S\subset V,s\in S,t\notin S} \bigoplus_{u\in S}W_u\),线性基即可

考虑原问题,每次加入一条边修改 \(O(1)\)\(W\)

转化为 \(O(n+m)\) 个数值每个在线性基中存在一段时间,每个时刻求最小值

容易做到 \(O(\sum (n+m)\log w)\)

代码:

#include <bits/stdc++.h>
using namespace std;
int n, m, s, t;
long long wt[1000010];
int p[1000010];
vector<pair<int, long long> > md[1000010];
struct linb {
    long long v[62];
    int t[62];
    void ins(long long x, int tm){
        for (int i = 59; ~i; --i)if (x >> i & 1){
            if (!v[i]){v[i] = x;t[i] = tm;return;}
            if (tm > t[i])swap(tm, t[i]), swap(v[i], x);
            x ^= v[i];
        }
    }
    long long min_with(long long x, int l) const {
        for (int i = 59; ~i; --i)if (t[i] >= l)x = min(x, x ^ v[i]);
        return x;
    }
};
long long ww[1000010];
void work(){
    cin >> n >> m >> s >> t;
    fill_n(wt + 1, n, 0);
    fill_n(p + 1, n, 1);
    for (int i = 1; i <= m; ++i)md[i].clear();
    auto psh = [&](int l, int r, long long w){if (l <= r && w)md[l].emplace_back(r, w);};
    for (int i = 1; i <= m; ++i){
        int u, v; long long w;
        cin >> u >> v >> w;
        if (u != v){
            auto del = [&](int u){if (u != s && u != t)psh(p[u], i - 1, wt[u]);};
            auto ins = [&](int u){if (u != s && u != t)p[u] = i;};
            del(u);wt[u] ^= w;ins(u); del(v);wt[v] ^= w;ins(v);
        }
        ww[i] = wt[s];
    }
    for (int i = 1; i <= n; ++i)if (i != s && i != t)psh(p[i], m, wt[i]);
    linb ln{};
    for (int i = 1; i <= m; ++i){
        for (auto [j, w] : md[i])ln.ins(w, j);
        cout << ln.min_with(ww[i], i) << "\n";
    }
}
int main(){
    freopen("mincut.in", "r", stdin);
    freopen("mincut.out", "w", stdout);
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int t;
    cin >> t;
    while (t--)work();
    return 0;
}

T2 \(\textcolor{black}\odot\) AT_agc034_f [AGC034F] RNG and XOR

题意

一个值初始为 \(0\),每次异或上 \([0,2^n)\) 中的一个数,选择 \(i\) 的概率为 \(\frac{p_i}{\sum p_i}\),对于每个 \(0\le i<2^n\) 求出第一次得到 \(i\) 的期望操作次数,\(n\le20\)

分析

先将 \(p\) 归一化

\(f_i\) 表示 \(i\) 的答案,则

\[f_0=0 \]

\[f_i=1+\sum_j p_j f_{i\oplus j}=1+\sum_{x\oplus y=i} p_xf_y=1+(p\ast f)_i \]

其中 \(\ast\) 表示异或卷积

由于 \(\sum p_i=1\),显然

\[\sum_i (p\ast f)_i=\sum_x \sum_y p_xf_y=\sum_i f_i \]

从而

\[\begin{aligned} (p\ast f)_0+\sum_{i\ne 0}(p\ast f)_i&=f_0+\sum_{i\ne 0}f_i\\ &=f_0+\sum_{i\ne 0}(1+(p\ast f)_i)\\ &=f_0+2^n-1+\sum_{i\ne 0}(p\ast f)_i\\ (p\ast f)_0&=f_0+2^n-1\\ \end{aligned} \]

\(p\ast f=f+V\),其中 \(V_0=2^n-1\)\(V_{\ne 0}=-1\)

\(p'_i=p_i-[i=0]\),则 \(p'\ast f=V\)

\(V\)\(p'\) 都是容易求的,容易通过 \(\text{FWT}\) 求出 \(f\)

总时间复杂度 \(O(n2^n)\)

代码

参考

T3 \(\textcolor{black}\odot\) P12478 [集训队互测 2024] Désive

题意

给定 \(a_{1\sim 2^n}\in[0,2^n)\)\(q\) 次询问,每次给定 \(l_1,r_1,l_2,r_2\),查询 \(\sum_{l_1\le l\le r_1}\sum_{l_2\le r\le r_2}f(l,r)\),其中 \(f(l,r)=\max_x \text{mex}_{i=l}^r (a_i\oplus x)\)\(n\le 18,q\le 10^6\)

分析

对于一个 \(f(l,r)\),将 \(a_{l\sim r}\) 加入一颗 \(\text{Trie}\) 中,在树上 \(dp\)

若一个叶子存在,则 \(dp\) 值为 \(1\),若一个结点左右子树中至少一个是满的则当前结点的 \(dp\) 值为左右子树之和,否则为左右子树的较大值,答案为根的 \(dp\)

暴力实现容易做到 \(O(4^nn+q)\)

将询问离线,差分并挂在右端点上,扫描线,则变为类似历史值之和的结构,考虑如何维护 \(f\)

前面暴力计算的过程,可以视为选择一个叶子删去它到根的链(不含它本身),答案为剩余的所有满的子树的大小之和

考虑 \(a\) 为排列的情况

右端点扫描过程中,对于每个叶子结点维护一个分段函数,自变量为左端点,函数值表示取当前叶子时对应区间的 \(f(l,r)\),显然对于一个右端点,每个叶子结点的分段函数至多 \(O(n)\) 段,对它 \(O(n)\) 个祖先都有贡献,总段数为 \(O(n^22^n)\)

实际上每个子树内部归并可以压缩到 \(O(n2^n)\)

每一段可以通过一次区间取 \(\max\) 实现,因此 \(O(n2^n)\) 次区间 \(\max\),并维护历史和,可以做到 \(O(n^22^n+nq)\)

然后考虑一般情况

插入一个右端点后,若一个子树内满的最大左端点从 \(t'\) 变为 \(t\),则需要考虑当前子树在 \((t',t]\) 内的分段函数的贡献

每个子树维护一个 \(\text{Trie}\),用开始时的方法,每次不断删去 \((t',t]\) 内的元素即可得到对应区间的分段

可以做到 \(O(n^22^n+nq)\)

代码

参考

比赛结果

\(100+16+35\)\(\text{rk}3\)

posted @ 2026-03-03 11:13  Hstry  阅读(1)  评论(0)    收藏  举报