voidInsert(int x){ int p = 0; for (int i = 25; i >= 0; --i) { int v = x >> i & 1; if (!trie[p][v]) { trie[p][v] = ++idx; } p = trie[p][v]; cnt[p]++; } }
从高位到低位依次取出每一位:
若对应儿子不存在,则新建节点
沿路径向下走
将经过节点的计数加一
插入完成后,x 所在路径上的所有节点计数均被正确维护。
2. 删除
1 2 3 4 5 6 7 8
voidDelete(int x){ int p = 0; for (int i = 25; i >= 0; --i) { int v = x >> i & 1; p = trie[p][v]; cnt[p]--; } }
删除时沿原路径走一遍,并将沿途 cnt 减一即可。 不必真正删除节点,只需保证计数正确。
该写法默认题目保证删除操作合法,即待删除元素一定存在。
七、排名查询 getRank
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
intgetRank(int x){ int p = 0, rank = 0; for (int i = 25; i >= 0; --i) { int v = x >> i & 1; if (v) { rank += cnt[trie[p][0]]; } p = trie[p][v]; if (!p) { break; } } return rank; }
该函数返回的不是题目中的“排名”,而是:
严格小于 x 的元素个数
因此主函数中查询排名时需要输出:
1
getRank(x + offset) + 1
正确性分析
从高位到低位考虑当前位。
设当前位为 v:
当 v = 0
若某个数在当前位取 1,则在高位前缀相同的前提下,它一定大于 x。 因此不会对“小于 x 的元素个数”产生贡献,直接沿 0 分支继续即可。
当 v = 1
此时,所有高位前缀与 x 相同、但当前位取 0 的数,一定严格小于 x。 因此可以直接累计:
1
rank += cnt[trie[p][0]];
随后继续沿 1 分支向下,统计剩余部分。
提前退出
若某一步 p 变为 0,说明当前前缀已不存在,后续更低位不可能再产生贡献,可以直接结束。
八、第 k 小查询 getVal
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13
intgetVal(int x){ int p = 0, val = 0; for (int i = 25; i >= 0; --i) { if (cnt[trie[p][0]] >= x) { p = trie[p][0]; } else { x -= cnt[trie[p][0]]; p = trie[p][1]; val |= 1 << i; } } return val; }
voidInsert(int x){ int p = 0; for (int i = 25; i >= 0; --i) { int v = x >> i & 1; if (!trie[p][v]) { trie[p][v] = ++idx; } p = trie[p][v]; cnt[p]++; } }
voidDelete(int x){ int p = 0; for (int i = 25; i >= 0; --i) { int v = x >> i & 1; p = trie[p][v]; cnt[p]--; } }
intgetRank(int x){ int p = 0, rank = 0; for (int i = 25; i >= 0; --i) { int v = x >> i & 1; if (v) { rank += cnt[trie[p][0]]; } p = trie[p][v]; if (!p) { break; } } return rank; }
intgetVal(int x){ int p = 0, val = 0; for (int i = 25; i >= 0; --i) { if (cnt[trie[p][0]] >= x) { p = trie[p][0]; } else { x -= cnt[trie[p][0]]; p = trie[p][1]; val |= 1 << i; } } return val; }