fjwc2019 D6T2 密文(trie+贪心)
设$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; }