UVa 1326 Jurassic Remains
题目描述
西伯利亚的古生物学家发现了一批侏罗纪时期的恐龙骨骼碎片。由于骨骼太大,无法整体运输,他们决定将骨骼拆分成单独的骨头,并在关节处用标签标记(标签为大写字母 A - Z),以便在博物馆重新组装。后来,他们又发现了一些新的骨头,并一并送往博物馆。
问题是:
- 标签不唯一,多个关节对可能使用相同字母标记
 - 新骨头上的标签不一定需要与其他骨头连接
 - 每块骨头上每个字母最多出现一次
 
任务:选择最多的骨头,使得在选出的骨头集合中,每个标签要么不出现,要么出现偶数次(这样才能让所有关节都配对连接)。
题目分析
关键条件转化
我们需要选出一个骨头子集,满足:
- 对于任意标签字母,它在所有选中骨头中出现的总次数为偶数
 - 子集大小最大化
 
关键观察:如果把每个骨头看作一个集合(元素是标签字母),那么条件等价于:所有选中骨头的对称差(异或)为空集。
数学模型
- 将 262626 个字母看作位掩码的 262626 位(
A对应第 000 位,Z对应第 252525 位) - 每块骨头转换为一个整数(掩码),某位为 111 表示包含对应字母
 - 问题转化为:从 NNN 个整数中选出一个子集,使得这些数的异或和为 000,且子集大小最大
 
解题思路
直接暴力法(可行但较慢)
枚举所有 2N2^N2N 个子集,检查异或和是否为 000,记录最大子集。
当 N≤24N \leq 24N≤24 时,224≈1.6×1072^{24} \approx 1.6 \times 10^7224≈1.6×107,在 OJ\texttt{OJ}OJ 上可能勉强通过。
折半搜索(优化方法)
将骨头分成两半:
- 前半部分:前 N2\frac{N}{2}2N 个骨头
 - 后半部分:剩余骨头
 
步骤:
- 枚举前半部分所有子集,记录每个异或值对应的最大子集大小和子集掩码
 - 枚举后半部分所有子集,对于每个异或值,在前半部分查找相同的异或值
- 如果找到,则合并子集,总大小 = 前半子集大小 + 后半子集大小
 - 更新最大子集
 
 - 特别处理前半或后半自身异或和为 000 的情况
 
复杂度:O(N×2N/2)O(N \times 2^{N/2})O(N×2N/2),对于 N=24N = 24N=24 只需约 24×212≈10524 \times 2^{12} \approx 10^524×212≈105 次操作,比暴力快约 200020002000 倍。
参考代码
// Jurassic Remains
// UVa ID: 1326
// Verdict: Accepted
// Submission Date: 2025-11-03
// UVa Run Time: 0.010s
//
// 版权所有(C)2025,邱秋。metaphysis # yeah dot net#include <bits/stdc++.h>
using namespace std;int main() {int N;while (cin >> N) {vector<int> bones(N);for (int i = 0; i < N; i++) {string s;cin >> s;int mask = 0;for (char c : s) {mask |= (1 << (c - 'A'));}bones[i] = mask;}int maxSize = 0;int bestMask = 0;// 折半搜索int half = N / 2;// 存储前半部分异或值 -> (size, mask)map<int, pair<int, int>> firstHalf;// 枚举前半部分for (int mask = 0; mask < (1 << half); mask++) {int xorSum = 0;int size = 0;for (int i = 0; i < half; i++) {if (mask & (1 << i)) {xorSum ^= bones[i];size++;}}// 如果当前异或值没出现过,或者出现但size更小,则更新if (firstHalf.find(xorSum) == firstHalf.end() || firstHalf[xorSum].first < size) {firstHalf[xorSum] = make_pair(size, mask);}// 如果前半部分自己就能满足条件if (xorSum == 0 && size > maxSize) {maxSize = size;bestMask = mask;}}// 枚举后半部分int secondHalf = N - half;for (int mask = 0; mask < (1 << secondHalf); mask++) {int xorSum = 0;int size = 0;for (int i = 0; i < secondHalf; i++) {if (mask & (1 << i)) {xorSum ^= bones[half + i];size++;}}// 查找前半部分是否有相同的异或值auto it = firstHalf.find(xorSum);if (it != firstHalf.end()) {int totalSize = it->second.first + size;if (totalSize > maxSize) {maxSize = totalSize;// 合并前后两部分的掩码bestMask = (it->second.second) | (mask << half);}}// 如果后半部分自己就能满足条件if (xorSum == 0 && size > maxSize) {maxSize = size;bestMask = mask << half;}}// 输出结果cout << maxSize << endl;if (maxSize > 0) {bool first = true;for (int i = 0; i < N; i++) {if (bestMask & (1 << i)) {if (!first) cout << " ";cout << (i + 1);first = false;}}cout << endl;} else {cout << endl;}}return 0;
}
