题意:给定n个只有大写字母组成的字符串,选取尽可能多的字符串,使得这些字符串中每个字母的个数都是偶数。n<=24
思路:直接枚举每个字符串的选或不选,复杂度是O(2^n)。其实还有更简便的方法。
对于每个字母,其实具体出现了多少次并不重要,重要的是奇数次还是偶数次,我们用0对应奇数次,1对应偶数次。对于每个字符串,我们就可以计算出对应的二进制数,方法如下。如果A出现奇数次,那么二进制数第一个位置为1,偶数次为0;如果B出现奇数次,那么二进制数第二个位置为1,偶数次为0……以此类推,每个位置都有一个对应的0或1。这样就组成了一个二进制数。所以我们就可以将题意转化为找到尽量多的数字,使得他们的异或和为0。
直接枚举复杂度是 O ( 2 n ) O(2^n) O(2n),但是我们不妨枚举前 n / 2 n/2 n/2个数字的选或不选,将所有可以得到的异或值存在一个STL的map中(键为异或和,值为得到这个异或和的选或不选的状态集合,对于同一个键,保留选取的数字最多的情况),然后枚举后 n / 2 n/2 n/2个数字的选或不选,计算出每个异或和,在map中查找是否有异或和等的键(因为两个相同的数字异或值为0),更新答案。
这样的复杂度只有 O ( 2 [ n / 2 ] ∗ l o g n ) O(2^{[n/2]} * logn) O(2[n/2]∗logn)。
#define ll long long
#define inf 0x3f3f3f3f
#define vec vector<int>
#define P pair<int,int>
#define MAX 25
int N, a[MAX];
string s[MAX];
vec res, t;
int main() {
ios::sync_with_stdio(false); cin.tie(0);
while (cin >> N) {
memset(a, 0, sizeof(a));
res.clear();
for (int i = 1; i <= N; i++) {
cin >> s[i];
for (int j = 0; j < s[i].size(); j++)
a[i] ^= 1 << (s[i][j] - 'a');//转化成二进制整数
}
map<int, vec> ma; ma[0] = res;
for (int i = 1; i < (1 << (N / 2)); i++) {//爆搜前一半
int tmp = 0, n = i; t.clear();
for (int j = 1; j <= N / 2 && n > 0; j++) {
if (n & 1)
tmp ^= a[j], t.push_back(j);
n >>= 1;
}
ma[tmp] = t;//记录出现的状态
}
for (int i = 1; i < (1 << (N - N / 2)); i++) {//爆搜后一半
int tmp = 0, n = i; t.clear();
for (int j = N / 2 + 1; j <= N && n > 0; j++) {
if (n & 1)
tmp ^= a[j], t.push_back(j);
n >>= 1;
}
if (ma.find(tmp) != ma.end() && t.size() + ma[tmp].size() > res.size()) {
res = ma[tmp];
for (int i = 0; i < t.size(); i++)res.push_back(t[i]);
}
}
printf("%d\n", res.size());
for (int i = 0; i < res.size(); i++)
printf("%d ", res[i]);
printf("\n");
}
}