题解 [CF1553H] XOR and Distance
啥也不会.jpg
- 和异或以后加加减减相关的问题尝试扔到 01-trie 上处理一下
- 当需要在 trie 树上查询多个取值的答案时可以尝试类似整体二分的将若干次查询一起处理
首先有个 trie 树暴力
枚举一个 \(x\in[0, 2^k)\),尝试在 trie 树上找到异或后差的最小值
那么需要遍历 trie 树
在每个节点,答案可能取在相同或不同子树
若取在相同子树可以递归处理
若取在不同子树,则一定是 1 的子树中 \(\oplus x\) 的最小值和 0 的子树中 \(\oplus x\) 的最大值
单次查询好像是 \(O(nk^2)\) 的
但是可以自底向上处理,那么每个子树内 \(\oplus x\) 的最大最小值是可以合并上来的
这样单次就是 \(O(nk)\) 的了
然后再怎么优化呢?
发现查询的 \(x\) 有很多位是相同的
那么尝试同时在树上查询 \([0, 2^k)\) 的答案
仍然考虑自底向上,那么在一个高度(子树内深度)为 \(i\) 的节点
它对应了 \([0, 2^i)\) 的答案
若取在相同子树,可以用 1 子树内 \(t\) 的答案更新 \(t+2^i\) 的答案
若取在不同子树,仍然可以对每个子树维护出子树内 \(\forall x\in[0, 2^i), \max/\min\{x\oplus 子树内元素\}\)
然后暴力枚举子树内 \(2^i\) 个值更新即可
复杂度 \(O(\sum 2^{k-i}2^i i)=O(2^kk^2)\)
然后实现的时候 trie 树没必要建出来
自底向上一层一层处理即可
这样是在每次考虑每次合并两个兄弟
那么一个 \(x\) 用两个自己子树内或两个兄弟子树内的去更新答案
其在当前这一位上的贡献被减法减掉了
写起来感觉稍难下手
啥?你问我代码为啥和 @x义x 的这么像?
复杂度 \(O(k2^k)\)
点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define N 1000010
#define ll long long
//#define int long long
char buf[1<<21], *p1=buf, *p2=buf;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf, 1, 1<<21, stdin)), p1==p2?EOF:*p1++)
inline int read() {
int ans=0, f=1; char c=getchar();
while (!isdigit(c)) {if (c=='-') f=-f; c=getchar();}
while (isdigit(c)) {ans=(ans<<3)+(ans<<1)+(c^48); c=getchar();}
return ans*f;
}
int n, k;
int a[N], f[N], mx[N], mn[N];
signed main()
{
n=read(); k=read();
memset(f, 0x3f, sizeof(f));
memset(mx, -0x3f, sizeof(mx));
memset(mn, 0x3f, sizeof(mn));
for (int i=1; i<=n; ++i) {
a[i]=read();
mx[a[i]]=mn[a[i]]=0;
}
for (int i=0; i<k; ++i) {
for (int y=0; y<(1<<k); ++y) if (y&(1<<i)) {
int x=y^(1<<i);
f[x]=f[y]=min(f[x], f[y]);
f[x]=min(f[x], mn[y]-mx[x]+(1<<i));
f[y]=min(f[y], mn[x]-mx[y]+(1<<i));
int mx_x=mx[x], mx_y=mx[y], mn_x=mn[x], mn_y=mn[y];
mx[x]=max(mx[x], mx_y+(1<<i));
mx[y]=max(mx[y], mx_x+(1<<i));
mn[x]=min(mn[x], mn_y+(1<<i));
mn[y]=min(mn[y], mn_x+(1<<i));
}
}
for (int i=0; i<(1<<k); ++i) printf("%d%c", f[i], " \n"[i==(1<<k)-1]);
return 0;
}