fjwc2019 D6T2 密文(trie+贪心)

#194. 「2019冬令营提高组」密文

设$s[i]$表示前$i$个密文的异或和

容易发现,只要知道$s[0]~s[n](s[0]=0)$就可以知道每一位的值。

转化一下,就变成了在完全图上求最小生成树,边权是$[l,r]$段的异或和

然鹅数据范围太大了......

但是边权是特殊的异或和!

于是我们用一棵trie维护边权,每次用贪心的思想

对于树上的某点,用最小的代价合并代表左右两个子树的连通块

合并时代价的计算直接暴力就好辣

可以证明每次的连通块数都减少一半

即复杂度为$O(nlognloga_{i})$

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
inline int min(int a,int b){return a<b?a:b;}
#define N 100005
#define M 3000005
int n,t,rt,u,a[N],ch[M][2],le[M],ri[M],h[N];
long long ans;
void ins(int &x,int d,int i){
    if(!x) x=++u;
    if(i>=0) ins(ch[x][(d>>i)&1],d,i-1);
}
int find(int x,int d,int i){
    if(i<0) return 0;
    int p=(d>>i)&1;
    return ch[x][p]?find(ch[x][p],d,i-1):find(ch[x][p^1],d,i-1)+(1<<i);
}
void dfs(int x,int d,int i){
    if(i<0){
        h[++t]=d; le[x]=ri[x]=t;
        return;
    }le[x]=t+1;
    int lc=ch[x][0],rc=ch[x][1],L,R,mn=1<<i;
    if(lc) dfs(lc,d,i-1);
    if(rc) dfs(rc,d|(1<<i),i-1);
    if(lc&&rc){
        if(ri[lc]-le[lc]<ri[rc]-le[rc]) L=lc,R=rc;
        else L=rc,R=lc;
        for(int j=le[L];j<=ri[L];++j)
            mn=min(mn,find(R,h[j],i-1));
        ans+=mn+(1<<i);
    }ri[x]=t;
}

int main(){
    freopen("secret.in","r",stdin);
    freopen("secret.out","w",stdout);
    scanf("%d",&n);
    for(int i=1;i<=n;++i)
        scanf("%d",&a[i]),a[i]^=a[i-1];
    for(int i=0;i<=n;++i) ins(rt,a[i],30);
    dfs(rt,0,30); printf("%lld",ans);
    return 0;
}

 

猜你喜欢

转载自www.cnblogs.com/kafuuchino/p/10547633.html
0条评论
添加一条新回复