POJ 1903 & ZOJ 2469 & UVA 1326 Jurassic Remains (部分枚举)
题意:给定n个只有大写字母组成的字符串,选取尽可能多的字符串,使得这些字符串中每个字母的个数都是偶数。n<=24
思路:直接枚举每个字符串的选或不选,复杂度是O(2^n)。其实还有更简便的方法。
对于每个字母,其实具体出现了多少次并不重要,重要的是奇数次还是偶数次,我们用0对应奇数次,1对应偶数次。对于每个字符串,我们就可以计算出对应的二进制数,方法如下。如果A出现奇数次,那么二进制数第一个位置为1,偶数次为0;如果B出现奇数次,那么二进制数第二个位置为1,偶数次为0……以此类推,每个位置都有一个对应的0或1。这样就组成了一个二进制数。所以我们就可以将题意转化为找到尽量多的数字,使得他们的异或和为0。
直接枚举复杂度是O(2^n),但是我们不妨枚举前n/2个数字的选或不选,将所有可以得到的异或值存在一个STL的map中(键为异或和,值为得到这个异或和的选或不选的状态集合,对于同一个键,保留选取的数字最多的情况),然后枚举后n/2个数字的选或不选,计算出每个异或和,在map中查找是否有异或和等的键(因为两个相同的数字异或值为0),更新答案。
这样的复杂度只有O(2^[n/2] * logn)。
#include<cstdio> #include<map> #define MAXN 30 using namespace std; int n,a[MAXN]; char s[1005]; map<int ,int > F; int bitcount(int x) {return x? bitcount(x/2)+(x&1):0;} //计算一个数二进制表示后所包含的1的个数 int main() { while(~scanf("%d",&n)) { for(int i=0;i<n;++i) { scanf("%s",s); a[i]=0; for(int j=0;s[j];++j) a[i]^=(1<<(s[j]-'A'));//将字符串转化成数 } F.clear();//每次记得清空映射 int n1=n/2,n2=n-n1; for(int i=0;i < (1<< n1);++i) //枚举前n/2个数的每种状态 { int x=0; for(int j=0;j<n1;++j) x^=((i>>j)&1)*a[j];//计算每种状态的异或和 if(F.count(x) || bitcount(i)>bitcount(F[x])) F[x]=i;//F记录每个异或值所对应的字符串选取状态 } int ans=0;//答案记录最终的选取状态 for(int i=0;i < (1<< n2);++i) //枚举后n/2个数的每种状态 { int x=0; for(int j=0;j<n2;++j) x^=((i>>j)&1)*a[j+n1];//计算每种状态的异或和 if(F.count(x) && bitcount(i)+bitcount(F[x])>bitcount(ans)) ans=(i<<n1)|F[x]; //如果找到异或和与前n/2个数中的相同的异或和,那么更新答案 } printf("%d\n",bitcount(ans)); for(int i=0;i<n;++i) if((ans>>i)&1) printf("%d ",i+1); printf("\n"); } return 0; }