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 ) 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");
	}
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值