题解:好题,就是看着书上的讲解也理解了好长时间。
在代码中给出详细的解释。
代码:
#include<stdio.h>
#include<iostream>
#include<stdlib.h>
#include<map>
using namespace std;
const int maxn=24;
map<int ,int >table;
int solo(int x)
{
return x==0?0:solo(x/2)+(x&1);//求对应的二进制有多少个1.
}
int n,A[maxn];
char s[1004];
int main()
{
while(~scanf("%d",&n))
{
if(n==0)
break;
for(int i=0;i<n;i++)
{
scanf("%s",s);
A[i]=0;
for(int j=0;s[j]!='\0';j++)//a[I]记录的为每个字符串转化为二进制再转为十进制下的数。例子ABD——>1101->1011->11
A[i]^=(1<<(s[j]-'A'));
}
table.clear();//table将每一个可能出现的异或值x和需要的字符串的个数形成映射。
int n1=n/2,n2=n-n1;//上面说的,中途相遇法,我们枚举前n/2
for(int i=0;i<(1<<n1);i++)//枚举出前n1个字符串的所有子集.例n1==5,则 00000<i<11111(二进制下)。则0或1就表示对应位的字符串是否在这个集合中出现(从后往前对应)例11001,就是编号为0,3,4的字符串构成一个集合的情况。
{
int x=0;
for(int j=0;j<n1;j++)//我们找到这几个字符串。求出它们的异或值。
{
if(i&(1<<j))
x^=A[j];
}
if(!table.count(x)||solo(table[x])<solo(i))//如果这个异或值之前没出现过,或者出现过但所用字符串的个数小于当前的跟新table[x]
table[x]=i;
}
int ans=0;
for(int i=0;i<(1<<n2);i++)//同上
{
int x=0;
for(int j=0;j<n2;j++)
{
if(i&(1<<j))
x^=A[n1+j];//注意,前面已经存在在n1个串,这里的j实际上是从n1开始的 ,所以异或的值是A[j+n1].
}
if(table.count(x)&&solo(ans)<solo(table[x])+solo(i))//如果后n2个串可以构成的x,在前面出现过,说明 这些串的异或和为0,也就是所有的字母出现的都为偶数次。更新ans。
ans=(i<<n1)^table[x];//i前面有n1个串。
}
printf("%d\n",solo(ans));
for(int i=0;i<n;i++)
{
if(ans&(1<<i))
printf("%d ",i+1);
}
printf("\n");
}
}
学到了如何枚举子集以及如何求元素是否在该子集出现的方法。
for(int I=0;i<(1<<n);I++)
{
for(int j=0;j<n;j++)
if(i&(1<<j))
}