Jurassic Remains --UVALive2965侏罗纪--状压+中途相遇法

题目链接https://vjudge.net/contest/306975#problem/I

在这里插入图片描述
在这里插入图片描述


题目大意:给你n(n<=24)个大写字母组成的字符串,选择尽量多的串使得每个大写字母出现的次数为偶数次。

emmmm,刚开始看到这题可能有个思路就是暴力,一个个选下去,也就是在前一个选择或不选择的基础上再次抉择,可以想到dfs,不过本人比较菜,dfs不过关,所以就用二进制枚举状态,而这也就是所谓的状态压缩了。我们刚开始的状态为0000,结束状态为1111,也就是全不选的状态到全选的状态。

那么中间怎么保证这些串的字母出现过偶数次呢?我们可以使用位运算与二进制的方便:设1表示奇数次,0表示偶数次,那么我们可以一直取 ^ 运算(相同为0,相异为1)来使得每个串的每个位是以01表示的:

for (int i=1; i<=n; i++) {
	scanf ("%s",s);
	nb[i]=0;
	int len=strlen(s);
	for (int j=0; j<len; j++)
		nb[i]^=(1<<(s[j]-'A'));
}

那么我们用一个整数就将一个字符串的字母出现次数的奇偶性表示了出来了。

接下来就是二进制枚举所有状态和选择了:

int ans=0,num=0,sta=0;
for (int i=0; i<(1<<n); i++) {
	int x=0,num=0;
	for (int j=1; j<=n; j++) {
		if (i&(1<<(j-1))) num++,x^=nb[j];//即第j个串有被选择
	}
	if (!x && num>ans) sta=i,ans=num;
}

那么整题的代码也就出来了:

#include <bits/stdc++.h>
using namespace std;
char s[1000];
int nb[30];
int main()
{
	int n;
	while (scanf ("%d",&n)!=EOF){
		for (int i=1; i<=n; i++){
			scanf ("%s",s);
			nb[i]=0;
			int len=strlen(s);
			for (int j=0; j<len; j++)
			   nb[i]^=(1<<(s[j]-'A'));
		}
		int ans=0,num=0,sta=0;
		for (int i=0; i<(1<<n); i++){
			int x=0,num=0;
			for (int j=1; j<=n; j++) {
				if (i&(1<<(j-1))) num++,x^=nb[j]; 
			}
			if (!x && num>ans) sta=i,ans=num; 
		}
		printf ("%d\n",ans);
		if (ans){
			for (int i=1; i<=n; i++){
			    if (sta&(1<<(i-1))) printf ("%d ",i);
		    }
		}	
		printf ("\n");
	}
	return 0;
}

但你会悲哀地发现,这样写会T掉,想想也知道复杂度是O(2n * n)差不多要跑3秒。。。接下来就是优化了,想想两个值相 ^ 为0那么这两个数是相等的,那么也就是说我们可以将n对半分了,前n/2个计算一下异或值,然后用map保存这些异或值所对应的状态,接下来对后n/2个计算异或值,查看map中是否存在这个异或值然后更新就好了

以下是AC代码:

#include <bits/stdc++.h>
using namespace std;
char s[1000];
int nb[30];
int getnb(int x,int y,int a,int b);
int main()
{
	int n;
	while (scanf ("%d",&n)!=EOF){
		for (int i=1; i<=n; i++){
			scanf ("%s",s);
			nb[i]=0;
			int len=strlen(s);
			for (int j=0; j<len; j++)
			   nb[i]^=(1<<(s[j]-'A'));
		}
		int ans=0,num=0,sta=0;
		int n1=n/2,n2=n-n1;
		map<int,int>q;
		for (int i=0; i<(1<<n1); i++){
			int x=0;
			for (int j=1; j<=n1; j++) {
				if (i&(1<<(j-1))) x^=nb[j]; 
			}
			q[x]=i;
		}
		for (int i=0; i<(1<<n2); i++){
			int x=0,num=0;
			for (int j=1; j<=n2; j++){
				if (i&(1<<(j-1))) x^=nb[j+n1];
			}
			if (q[x]) {
				num=getnb(q[x],i,n1,n2);//计算这两个状态有多少个1
				if (num>ans) {
					ans=num;
					sta=(i<<n1)^q[x];//状态i向前移动n1位和q[x]中的状态相异或
				}
			}
		}
		printf ("%d\n",ans);
		if (ans){
			for (int i=1; i<=n; i++){
			    if (sta&(1<<(i-1))) printf ("%d ",i);
		    }
		}	
		printf ("\n");
	}
	return 0;
}
int getnb(int x,int y,int a,int b)
{
	int sum=0;
	for (int i=0; i<a; i++){
		if (x&(1<<i)) sum++;
	}
	for (int i=0; i<b; i++){
		if (y&(1<<i)) sum++;
	}
	return sum;
}

emmm,这个只跑了29ms。。。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值