解析
有的时候,看起来是暴力的东西再稍微想想性质就是正解了。
本题有两种做法,一种使用了trie树,一种没有。但本质是一样的,只是trie树把我们的所求显性的表达了出来。
考虑trie树暴力怎么做。
对于一个特定的
x
x
x 走到一个结点时,有两种选择:
- 两个数都在某一棵子树里选择。
- 两个数分别在两棵子树中选择。
对于第一种,递归寻找答案,对于第二种,因为它们在当前结点已经产生了
2
k
2^k
2k 的异或,因此接下来递归一定是同向子树尽可能大,异向子树尽可能小。
直接做单次复杂度是
O
(
n
k
)
O(nk)
O(nk) 的,无法接受。
注意到,如果
a
i
,
a
j
a_i,a_j
ai,aj 二进制的一些前缀相同,那么
x
x
x 的这些位是什么并不影响最终结果。
那么回到刚才的第一种选择,我们如果这是第
i
i
i 层的结点,递归寻找的答案前
i
i
i 位必然相同,那么我们其实并不关心这些位
x
x
x 的取值,也就是说,一共只有
2
k
−
i
2^{k-i}
2k−i 种答案是我们需要关注的。
类似的,第二种选择所需要的 m n , m x mn,mx mn,mx 也只关心第 i i i 位之后的取值。
这些取值状态的总数是
O
(
2
k
k
)
O(2^kk)
O(2kk) 的,不难通过分析从两棵子树的状态
O
(
1
)
O(1)
O(1) 得到其父亲各状态的转移。
总复杂度
O
(
2
k
k
)
O(2^kk)
O(2kk)。
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define debug(...) fprintf(stderr,__VA_ARGS__)
#define ok debug("OK\n")
using namespace std;
const int N=6e5+100;
const int inf=1e9;
inline ll read(){
ll x(0),f(1);char c=getchar();
while(!isdigit(c)) {if(c=='-')f=-1;c=getchar();}
while(isdigit(c)) {x=(x<<1)+(x<<3)+c-'0';c=getchar();}
return x*f;
}
int n;
int mi[25],mn[20][N],mx[20][N],res[20][N];
signed main(){
#ifndef ONLINE_JUDGE
freopen("a.in","r",stdin);
freopen("a.out","w",stdout);
#endif
n=read();int lim=read();
mi[0]=1;
for(int i=1;i<=lim;i++) mi[i]=mi[i-1]<<1;
for(int i=0;i<mi[lim];i++){
mn[0][i]=inf;mx[0][i]=-inf;res[0][i]=inf;
}
for(int i=1;i<=n;i++){
int x=read();
mn[0][x]=mx[0][x]=0;
}
for(int k=1;k<=lim;k++){
for(int x=0;x<mi[lim];x++){
int y=x^mi[k-1];
mn[k][x]=min(mn[k-1][x],mn[k-1][y]+mi[k-1]);
mx[k][x]=max(mx[k-1][x],mx[k-1][y]+mi[k-1]);
res[k][x]=min(min(res[k-1][x],res[k-1][y]),mn[k-1][y]+mi[k-1]-mx[k-1][x]);
//printf("k=%d x=%d y=%d mn=%d mx=%d res=%d\n",k,x,y,mn[k][x],mx[k][x],res[k][x]);
}
}
for(int i=0;i<mi[lim];i++) printf("%d ",res[lim][i]);
return 0;
}
/*
*/