「2019冬令营提高组」密文

传送门

考虑前缀异或和 $b[i]$

如果知道每个 $b[i]$ 就相当于知道所有数

初始知道 $b[0]$,每次操作 $l,r$ 就是求出 $b[l-1] xor b[r]$

考虑转化成图论模型,把 $b[i]$ 看成点(包括 $b[0]$),每次操作相当于把两个点 $b[l-1],b[r]$ 连边,边权为 $b[l-1] xor b[r]$

当整个图变成一个联通块时就相当于求出了所有的 $b[i]$

要求图联通时代价最小,显然考虑 prim 求最小生成树

对于一个点 $u$,找一个点 $v$ 使得边权最小,显然可以用 $trie$ 来找

但是每次都暴力搞还是太慢了,考虑贪心

对于 $trie$ 上的一个节点,如果只考虑左儿子的联通块和右儿子的联通块合并,那么最小代价必然是左儿子子树中的一个叶子节点和右儿子子树中的一个叶子节点连起来

那么对 $trie$ 上的每个节点 $u$,递归左右儿子,先让左右儿子的子树变成联通块,然后,枚举它左子树的每个叶子节点,然后暴力往右子树中查询最小值,最后把最小代价加到答案里

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<vector>
using namespace std;
typedef long long ll;
inline int read()
{
    int x=0,f=1; char ch=getchar();
    while(ch<'0'||ch>'9') { if(ch=='-') f=-1; ch=getchar(); }
    while(ch>='0'&&ch<='9') { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
    return x*f;
}
const int N=1e5+7;
const ll INF=1e18+7;
int n,b[N];
int ch[N*31][2],rt,tot;//trie
vector <int> G[N*31];//G存每个节点的子树所有叶子代表的b
void insert(int &u,int x,int d)//插入
{
    if(!u) u=++tot;
    G[u].push_back(x);
    if(d>=0) insert(ch[u][ (x>>d)&1 ],x,d-1);
}
int query(int u,int x,int d)//查询
{
    if(d<0) return 0;
    if(ch[u][ (x>>d)&1 ]) return query(ch[u][ (x>>d)&1 ],x,d-1);
    else return query(ch[u][ ((x>>d)&1)^1 ],x,d-1) + (1<<d);
}
ll ans;
void solve(int u,int d)//递归处理
{
    if(d<0) return;
    if(ch[u][0]) solve(ch[u][0],d-1);
    if(ch[u][1]) solve(ch[u][1],d-1);
    if(ch[u][0]&&ch[u][1])
    {
        ll res=INF; int lc=ch[u][0],rc=ch[u][1];
        for(int i=G[lc].size()-1;i>=0;i--)
            res=min(res,1ll*query(rc,G[lc][i],d-1));
        ans+=res+(1<<d);
    }
}
int main()
{
    freopen("secret.in","r",stdin);
    freopen("secret.out","w",stdout);
    n=read();
    for(int i=1;i<=n;i++) b[i]=b[i-1]^read();
    for(int i=0;i<=n;i++) insert(rt,b[i],29);//注意b[0]也要插入
    solve(rt,29);
    printf("%lld",ans);
    return 0;
}
原文地址:https://www.cnblogs.com/LLTYYC/p/10548565.html