01-Trie 学习
其实就是把二进制数放在 Trie 上面,然后就可以解决很多问题
最大 xor 数对
就是把所有的数插到 Trie 上,每次查询每一位上有不同就走不同的边
#include <algorithm>
#include <cstdio>
using namespace std;
const int maxn = 3e5 + 10;
int n,tot,ans,ch[maxn*31][2];
inline void insert(int x) {
int u = 0;
for (int i = 31;~i;i--) {
int bit = (x>>i)&1;
if (!ch[u][bit]) ch[u][bit] = ++tot;
u = ch[u][bit];
}
}
inline int query(int x) {
int u = 0,ans = 0;
for (int i = 31;~i;i--) {
int bit = (x>>i)&1;
if (ch[u][bit^1]) { u = ch[u][bit^1]; ans |= 1<<i; }
else u = ch[u][bit];
}
return ans;
}
int main() {
scanf("%d",&n);
for (int i = 1,x;i <= n;i++) { scanf("%d",&x); insert(x); ans = max(ans,query(x)); }
printf("%d",ans);
return 0;
}
树上最大 xor 路径
记一个 \(d_i\) 为 \(i\) 到根的 xor
就变成最大的 \(\{d\}\) 点对了
CF923C Perfect Security
题意:给两个序列 \(\{a\}\) 和 \(\{b\}\),求字典序最小的 \(\{c\}\) 使得 \(a_i\ xor\ c_i = b_i\)
字典序最小要求 \(c_i = a_i\ xor\ b_i\) 尽量小,所以按顺序找 \(a_i\ xor\ b_i\) 最小,然后删除 \(b_i\) 即可
#include <cstdio>
const int maxn = 3e5 + 10;
int n,tot(1),a[maxn],ch[maxn*30][2],siz[maxn*30];
inline void insert(int u,int i,int x) {
if (i < 0) return void(siz[u]++);
int bit = (x>>i)&1;
if (!ch[u][bit]) ch[u][bit] = ++tot;
insert(ch[u][bit],i-1,x);
siz[u] = siz[ch[u][0]]+siz[ch[u][1]];
}
inline int query(int x) {
int u = 1,ans = 0;
for (int i = 30;~i;i--) {
int bit = (x>>i)&1; siz[u]--;
if (ch[u][bit] && siz[ch[u][bit]]) u = ch[u][bit];
else { u = ch[u][bit^1]; ans |= 1<<i; }
}
siz[u]--; //!!!
return ans;
}
int main() {
scanf("%d",&n);
for (int i = 1;i <= n;i++) scanf("%d",&a[i]);
for (int i = 1,x;i <= n;i++) { scanf("%d",&x); insert(1,30,x); }
for (int i = 1;i <= n;i++) printf("%d ",query(a[i]));
return 0;
}
CF842D Vitya and Strange Lesson
题意:给一个数列,每次操作将所有数 \(xor\ x\),求操作后的 Mex
在 Trie 树上,若 0 边连的子树不是满的,Mex 就在 0 边连的子树中,否则在 1 边连的子树中
再是 xor,发现 \(x\) 的某一位上是 1,就会交换 Trie 树的左右子树,但是不要真的交换,找 Mex 的时候走相反的边就好了
注意这题的 siz 不再是子树内有多少个数,而是有多少个叶子节点有数(即相同的数只算一遍)
#include <cstdio>
const int maxn = 3e5 + 10;
int n,m,tot(1),ch[maxn*21][2],siz[maxn*21];
inline void insert(int u,int i,int x) {
if (i < 0) return void(siz[u] = 1); //!!! =1
int bit = (x>>i)&1;
if (!ch[u][bit]) ch[u][bit] = ++tot;
insert(ch[u][bit],i-1,x);
siz[u] = siz[ch[u][0]]+siz[ch[u][1]];
}
inline int query(int x) {
int u = 1,ans = 0;
for (int i = 20;~i;i--) {
int bit = (x>>i)&1;
if (siz[ch[u][bit]] == (1<<i)) {
u = ch[u][bit^1];
ans |= 1<<i;
} else u = ch[u][bit];
if (!u) return ans;
}
return ans;
}
int main() {
scanf("%d%d",&n,&m);
for (int x;n--;insert(1,20,x)) scanf("%d",&x);
for (int x = 0,y;m--;printf("%d\n",query(x ^= y))) scanf("%d",&y);
return 0;
}
CF1416C XOR Inverse
题意:给一个数列 \(\{a\}\),求一个最小的 \(x\) 使得 \(a_i = a_i\ xor\ x\) 后 \(\{a\}\) 的逆序对最少
和上题一样,若 \(x\) 的某一位上是 1,就相当于交换了那一位那层的 Trie 的所有左右子树
再考虑哪些位上要交换,可以发现 Trie 树叶子上,一个点在另一个点左边,那右边这个点一定小于左边的点
那可以将每一个数依次插入 Trie 上,Trie 上的结点这个子树叶子的结点的编号
可以发现叶子节点编号的逆序对就是原数组的逆序对,因为原数组若一个数后面有比它小的数,小的数会比它后加入并且插入 Trie 的叶子后在它的前面
因为两颗子树内部数的顺序是不影响两棵之间的逆序对个数的,所以就可以计算每一层所有子树反转或不反转的逆序对个数了
#include <algorithm>
#include <cstdio>
#include <vector>
using namespace std;
const int maxn = 3e5 + 10;
const int N = 30;
vector<int> num[maxn*N];
int n,tot,ch[maxn*N][2];
long long cnt[N+1][2];
inline void insert(int x,int v) {
int u = 0;
for (int i = N;~i;i--) {
int bit = (x>>i)&1;
num[u].push_back(v);
if (!ch[u][bit]) ch[u][bit] = ++tot;
u = ch[u][bit];
}
num[u].push_back(v);
}
inline void solve(int u,int i) {
if (i < 0) return;
if (ch[u][0]) solve(ch[u][0],i-1);
if (ch[u][1]) solve(ch[u][1],i-1);
if (!ch[u][0] || !ch[u][1]) return;
long long res = 0;
for (size_t j = 0;j < num[ch[u][1]].size();j++)
res += num[ch[u][0]].end()-lower_bound(num[ch[u][0]].begin(),num[ch[u][0]].end(),num[ch[u][1]][j]);
cnt[i][0] += res;
cnt[i][1] += 1ll*num[ch[u][0]].size()*num[ch[u][1]].size()-res;
}
int main() {
scanf("%d",&n);
for (int i = 1,x;i <= n;i++) { scanf("%d",&x); insert(x,i); }
solve(0,N);
long long ans = 0;
int res = 0;
for (int i = 0;i <= N;i++)
if (cnt[i][0] <= cnt[i][1]) ans += cnt[i][0];
else { ans += cnt[i][1]; res |= 1<<i; }
printf("%lld %d",ans,res);
return 0;
}