Xor-MST 更优秀的做法
Xor-MST 更优秀的做法。
转换部分略过,重点讲优化。转换后,对于 trie 上的每一个节点,你需要求出它左右子树中异或的最小值。
目前有两种做法,一种是把一棵子树内所有数插入另一棵子树,另一种是两棵子树同时遍历,能同侧递归就同侧递归,否则异侧递归,这种做法不把 trie 建出来也行。
分析一下复杂度,由于树高是
看上去就很能优化。
注意到,trie 上有用的节点只有
实现上,每个节点需要存下它的深度和它代表的从根到这个节点的路径(用一个数表示)。往下跳时直接选一个儿子跳,每次会跳过一段。
先放一份实现得很史的代码:
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<vector>
using namespace std;
const int NN=500000,L=30,K=2,INF=2000000000;
int d[NN],vv[NN][K],trans[NN][K],b[L+1],vvv[NN],mi,pp[NN];
long long ans;
bool ss;
int work(int l,int r)
{
return b[r-l]<<l+1;
}
void init(int p)
{
if (trans[p][0])
{
vv[p][0]&=work(d[trans[p][0]],d[p]);
vvv[trans[p][0]]=vvv[p]^vv[p][0];
init(trans[p][0]);
}
if (trans[p][1])
{
vv[p][1]&=work(d[trans[p][1]],d[p]);
vvv[trans[p][1]]=vvv[p]^vv[p][1];
init(trans[p][1]);
}
}
void dfs(int p)
{
int ppp,to,p1,p2;
bool k;
ppp=pp[p];
if (d[ppp]>-1)
{
k=vvv[p]>>d[ppp]&1;
while (1)
{
to=trans[ppp][k];
if (!to||d[to]<d[p]) break;
ppp=to;
if (d[ppp]>d[p]) k=vvv[p]>>d[ppp]&1;
}
}
if (d[p]>-1)
{
p1=trans[p][0];
pp[p1]=ppp;
dfs(p1);
p2=trans[p][1];
pp[p2]=ppp;
dfs(p2);
}
else mi=min(mi,vvv[p]^vvv[ppp]);
}
void trie(int p)
{
if (trans[p][0]) trie(trans[p][0]);
if (trans[p][1]) trie(trans[p][1]);
if (trans[p][0]&&trans[p][1])
{
mi=INF;
pp[trans[p][0]]=trans[p][1];
dfs(trans[p][0]);
ans+=mi;
}
}
int main()
{
int n,tot,i,a,j,p,v;
bool k;
scanf("%d",&n);
ss=n==800;
d[0]=L-1;
tot=1;
for (i=0;i<n;i++)
{
scanf("%d",&a);
p=0;
for (j=L-1;j>-1;j--)
{
if (d[p]>j)
{
v=vv[p][k];
if ((a>>j&1)!=(v>>j&1))
{
d[tot]=j;
vv[tot][a>>j&1]=a;
vv[tot][v>>j&1]=v;
trans[tot][a>>j&1]=tot+1;
trans[tot][v>>j&1]=trans[p][k];
trans[p][k]=tot++;
d[tot++]=-1;
break;
}
}
else
{
if (!trans[p][a>>j&1])
{
d[tot]=-1;
vv[p][a>>j&1]=a;
trans[p][a>>j&1]=tot++;
break;
}
k=a>>j&1;
}
if (d[trans[p][k]]==j-1) p=trans[p][k];
}
}
for (i=1;i<=L;i++) b[i]=(1<<i)-1;
init(0);
trie(0);
printf("%lld",ans);
return 0;
}
这份代码可读性低(我已经看不懂了),跑起来也不那么快。
简单解释一下,建 trie 时是暴力插入,init 函数是预处理节点信息的,后面的思路大概是上面讲的第一种,遍历左子树,对于其中每一个节点维护其对应的右子树中的节点,转移到儿子时在右子树暴力跳。
有没有更好的实现方法呢?
先看建树(和上面的代码没有任何关系):
int build(int l,int r,int al,int ar,int deep)
{
int mid,id,amid;
if (l==r)
{
d[t]=deep;
v[t]=a[al];
return t++;
}
mid=l+r>>1;
if (a[ar]<=mid) return build(l,mid,al,ar,deep-1);
if (a[al]>mid) return build(mid+1,r,al,ar,deep-1);
id=t++;
d[id]=deep;
amid=upper_bound(a+al,a+ar,mid)-a-1;
e[id][0]=build(l,mid,al,amid,deep-1);
e[id][1]=build(mid+1,r,amid+1,ar,deep-1);
v[id]=v[e[id][0]];
return id;
}
思想上类似于线段树或整体二分。
如果区间中的数集中在一侧就直接向一侧递归,否则二分出断开的位置。递归次数
再看查询:
int work(int rt1,int rt2)
{
int rt3,rt4;
if (d[rt1]==-1&&d[rt2]==-1) return v[rt1]^v[rt2];
if (d[rt1]==d[rt2]) return min(work(e[rt1][0],e[rt2][0]),work(e[rt1][1],e[rt2][1]));
rt3=rt1;
rt4=rt2;
if (d[rt3]<d[rt4]) swap(rt3,rt4);
return work(e[rt3][v[rt4]>>d[rt3]&1],rt4);
}
用的是上面讲的第二种,按深度简单分讨一下。
那么,结束了,上完整代码:
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<vector>
using namespace std;
char buf[1 << 20], *p1 = buf, *p2 = buf;
#define getc() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 20, stdin), p1 == p2) ? EOF : *p1++)
#define putc(x) putchar(x)
inline int read() {
int x = 0, sgn = 0; char s = getc();
while(!isdigit(s)) sgn |= s == '-', s = getc();
while(isdigit(s)) x = x * 10 + s - '0', s = getc();
return sgn ? -x : x;
}
inline void print(long long x) {if(x >= 10) print(x / 10); putc(x % 10 + '0');}
const int N=400000,V=1073741823,K=2,D=29;
int a[N],t,d[N],v[N],e[N][K];
int build(int l,int r,int al,int ar,int deep)
{
int mid,id,amid;
if (l==r)
{
d[t]=deep;
v[t]=a[al];
return t++;
}
mid=l+r>>1;
if (a[ar]<=mid) return build(l,mid,al,ar,deep-1);
if (a[al]>mid) return build(mid+1,r,al,ar,deep-1);
id=t++;
d[id]=deep;
amid=upper_bound(a+al,a+ar,mid)-a-1;
e[id][0]=build(l,mid,al,amid,deep-1);
e[id][1]=build(mid+1,r,amid+1,ar,deep-1);
v[id]=v[e[id][0]];
return id;
}
int work(int rt1,int rt2)
{
int rt3,rt4;
if (d[rt1]==-1&&d[rt2]==-1) return v[rt1]^v[rt2];
if (d[rt1]==d[rt2]) return min(work(e[rt1][0],e[rt2][0]),work(e[rt1][1],e[rt2][1]));
rt3=rt1;
rt4=rt2;
if (d[rt3]<d[rt4]) swap(rt3,rt4);
return work(e[rt3][v[rt4]>>d[rt3]&1],rt4);
}
int main()
{
int n,i;
long long ans;
n=read();
for (i=0;i<n;i++) a[i]=read();
sort(a,a+n);
build(0,V,0,n-1,D);
ans=0;
for (i=0;i<t;i++) if (d[i]>-1) ans+=work(e[i][0],e[i][1]);
print(ans);
return 0;
}
小清新很多,跑到 CF 第一页,略逊于卡常巨佬。
我与 Xor-MST 的故事。