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;
}

 

你可能感兴趣的:(main)