基因治疗领域的研究中,医生们经常需要评估不同基因组合的治疗效果。在这个问题中,我们面对的是一个典型的博弈论场景:两位医生小蓝和小乔需要从n个候选基因中各选一个进行组合评估,组合效果由两个基因特性数值的异或结果决定。
异或运算(XOR)是一种二进制位运算,当两个对应位不同时结果为1,相同时为0。例如:5(101)⊕3(011)=6(110)
01字典树是一种特殊的二叉树结构,专门用于处理二进制数的存储和查询。每个节点包含:
cpp复制class C2BNumTrieNode {
public:
C2BNumTrieNode* m_childs[2]; // 0和1分支
int m_iNum = 0; // 经过该节点的数字数量
};
从最高位开始,按二进制位逐层构建树结构:
cpp复制void Add(T iNum) {
C2BNumTrieNode* p = m_pRoot;
for(int i=iLeveCount-1; i>=0; i--) {
p->m_iNum++;
bool bRight = iNum & ((T)1 << i); // 获取当前位
if(!p->m_childs[bRight]) {
p->m_childs[bRight] = new C2BNumTrieNode();
}
p = p->m_childs[bRight];
}
p->m_iNum++;
}
利用贪心算法,尽可能选择与当前位相反的路径:
cpp复制T MaxXor(T iNum) {
C2BNumTrieNode* p = m_pRoot;
T iRet = 0;
for(int i=iLeveCount-1; i>=0; i--) {
bool bRight = !(iNum & ((T)1 << i)); // 期望的相反位
bool bSel = p->GetNot0Child(bRight); // 优先选择相反位
p = p->m_childs[bSel];
if(bSel == bRight) {
iRet |= ((T)1 << i); // 设置结果位
}
}
return iRet;
}
cpp复制pair<int,int> Ans(vector<int>& a) {
CMaxXor2BTrie<> trie;
for(auto& i : a) trie.Add(i);
int iMin = INT_MAX, iMax = INT_MIN;
for(auto& i : a) {
trie.Del(i);
int curMax = trie.MaxXor(i);
int curMin = (~i) & ((1LL<<31)-1); // 位反转处理
curMin = trie.MaxXor(curMin) ^ curMin ^ i;
iMin = min(iMin, curMax);
iMax = max(iMax, curMin);
trie.Add(i);
}
return {iMax, iMin};
}
对于最大10^5的数据规模,完全可以在1秒内完成计算。
最小异或值不能直接通过字典树查询得到,需要巧妙转换:
(tmp ^ not_i) ^ i 得到实际的最小异或值cpp复制int not1 = (~i) & mask; // 保留有效位
int tmp = trie.MaxXor(not1);
int curMin = (tmp ^ not1) ^ i;
使用指针而非数组实现树结构,可以动态分配内存,避免预分配过大空间。同时需要注意:
考虑边界情况和特殊场景:
cpp复制TEST_METHOD(TestAllSame) {
vector<int> a = {5,5,5,5};
auto res = Solution().Ans(a);
Assert::AreEqual(0, res.first); // 任何组合异或都是0
Assert::AreEqual(0, res.second);
}
TEST_METHOD(TestMinMax) {
vector<int> a = {1, INT_MAX};
auto res = Solution().Ans(a);
Assert::AreEqual(INT_MAX-1, res.first);
Assert::AreEqual(INT_MAX-1, res.second);
}
01字典树不仅适用于此类博弈问题,还可应用于:
在实际工程中,这种数据结构可用于:
与其他方法相比,01字典树的优势:
| 方法 | 时间复杂度 | 空间复杂度 | 适用场景 |
|---|---|---|---|
| 暴力枚举 | O(n²) | O(1) | 小数据量(n<1000) |
| 排序+贪心 | O(nlogn) | O(n) | 特定排序特征 |
| 01字典树 | O(nlogM) | O(nlogM) | 大规模数据,M为数字位数 |
在实际编程竞赛中,当n>10,000时,01字典树通常是唯一可行方案。