[FZOJ194] 「2019冬令营提高组」密文(Trie+贪心)

题意有一个序列a\rm aa,你可以花费vl xor vl+1 xor...xor vr−1xor vr\rm{v_l \ xor\ v_{l+1}\ xor...xor \ v_{r-1} xor \ v_{r}}vl​ xor vl+1​ xor...xor vr−1​
摘要由CSDN通过智能技术生成

题意

  • 有一个序列 a \rm a a,你可以花费 v l   x o r   v l + 1   x o r . . . x o r   v r − 1 x o r   v r \rm{v_l \ xor\ v_{l+1}\ xor...xor \ v_{r-1} xor \ v_{r}} vl xor vl+1 xor...xor vr1xor vr的代价询问 a l   x o r   a l + 1   x o r . . . x o r   a r − 1   x o r   a r \rm a_l \ xor\ a_{l+1}\ xor...xor\ a_{r-1}\ xor\ a_r al xor al+1 xor...xor ar1 xor ar,求确定序列 a \rm a a的最小代价。( n ≤ 1 0 5 \rm n \le 10^5 n105

知道序列 a \rm a a等价于知道了 ∀ i ∈ [ 0 , n ] \rm \forall i\in[0,n] i[0,n] s i = a 1   x o r   a 2   x o r . . . a i − 1   x o r   a i \rm s_i= a_1\ xor\ a_2\ xor...a_{i - 1} \ xor\ a_i si=a1 xor a2 xor...ai1 xor ai。那么每次询问 [ l , r ] \rm[l,r] [l,r]就相当于知道了 s l − 1   x o r   s r \rm s_{l - 1} \ xor \ s_r sl1 xor sr,我们可以对任意两个 s i \rm s_i si连询问他们两个的代价的边,因为确定 n \rm n n个未知数需要 n \rm n n个方程,那么这个张图的最小生成树的权值和就是确定序列的最小代价(已知 s 0 = 0 ) \rm s_0 = 0) s0=0

我们考虑克鲁斯卡尔的过程,每次找到当前权值最小的边,连接两个连通块,因为这里的运算为异或,我们用一个Trie来寻找当前权值最小的边,边的总数是 O ( n 2 ) \rm O( n^2) O(n2)级别的,但实际上我们按照Trie自底向上连边的话,任意两个点所连的边只会在他们的LCA处作为连接LCA左右两个子树之间两个连通块的边,我们按照启发式合并的思想枚举子树大小较小的所有点,在另一边所在的Trie上查询异或最小值,复杂度就是 O ( n log ⁡ n log ⁡ 1 0 9 ) \rm O(n \log n\log10^9) O(nlognlog109)

#include <bits/stdc++.h>

using namespace std;

const int N = 1e7 + 3;

struct Trie {
    int ch[N][2], size[N], dis[N], val[N], cnt;

    void insert(int x) {
        int now = 0;
        for (int i = 30; ~i; --i) {
            int to = x >> i & 1;
            if (!ch[now][to])
                ch[now][to] = ++cnt;
            now = ch[now][to], ++size[now];
        }
    }

    queue<int> q;

    int query(int s, int t, int d) {
        if (!size[s] || !size[t])
            return 0;
        if (size[s] > size[t])
            swap(s, t);

        int ans = INT_MAX;

        for (q.push(s); !q.empty(); q.pop()) {
            int u = q.front();
            if (dis[u] > d) {
                int now = t;
                for (int i = d; ~i; --i) {
                    int to = val[u] >> i & 1;
                    if (!ch[now][to])
                        to ^= 1;
                    now = ch[now][to], val[u] ^= to << i;
                }
                ans = min(ans, val[u] | (1 << (d + 1)));
            } else {
                for (int j = 0; j < 2; ++j) {
                    if (size[ch[u][j]]) {
                        val[ch[u][j]] = val[u] | (j << (d - dis[u]));
                        dis[ch[u][j]] = dis[u] + 1, q.push(ch[u][j]);
                    }
                }
            }
            dis[u] = val[u] = 0;
        }
        return ans;
    }

    void dfs(int now, int d) {
        static long long ans = 0;
        if (size[ch[now][0]] > 1)
            dfs(ch[now][0], d - 1);
        if (size[ch[now][1]] > 1)
            dfs(ch[now][1], d - 1);
        ans += query(ch[now][0], ch[now][1], d - 1);
        if (!now)
            printf("%lld\n", ans);
    }

} T;

int n, s[N];

int main() {
    freopen("secret.in", "r", stdin);
    freopen("secret.out", "w", stdout);

    scanf("%d", &n);
    for (int i = 1; i <= n; ++i) {
        scanf("%d", &s[i]);
        s[i] ^= s[i - 1];
        T.insert(s[i]);
    }
    T.insert(0), T.dfs(0, 30);

    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值