您的位置:首页 > 大数据 > 人工智能

LA --- 2965 Jurassic Remains 数相同的大写字母 【思维 + 状态压缩枚举 + 中途相遇法(折半搜索)】

2017-08-18 10:32 471 查看
传送门

//题意 : 给你n个由大写字母组成的字符串, 从中选取尽量多的串使得选中的串中出现了的大写字母的出现次数都是偶数. 输出可以选的最大值和具体选的那些串.

//思路 : 首先是思维转化, 我们发现一个大写字母出现了几次不重要, 出现的次数为奇数还是偶数才是比较重要的. 那么一个字母的状态无非就两种, 奇数或偶数, 那么我们就用二进制来表示, 1表示出现了奇数次, 0表示出现了偶数次, 如第二个样例 :

A B C D E F G

1 1 0 1 0 0 0 —ABD

0 0 0 0 1 0 1 —EG

0 0 0 0 1 0 1 —GE

1 1 0 0 1 0 0 —ABE

1 0 1 0 0 0 0 —AC

0 1 1 1 0 0 0 —BCD

所以现在问题转化为了从中选取尽量多的数使得他们的异或值为0, 注意异或值为0的一定是相同的两个数. 那么最简单的方法就是暴力O(2^n), 如果n达到了30左右肯定是不行的, 所以就用到了中途相遇法, 很神奇的, 也叫折半枚举, 即把n个串分成两堆, 那么我们分开进行二进制枚举, 先把上半部分所有可能的异或值全部求出来并标记, 然后以同样的方法求另一部分, 如果遇到了上面出现过的, 就更新答案. 这样是可以保证得的除答案的, 就算全部是选上方的, 枚举下面时我们也是枚举了0的情况的, 也就是全部都选上面的 , 所以这种方法就可行的. 并且复杂度降为O(√2^nlogn)这个复杂度是可以接受的.

AC Code

/** @Cain*/
const int maxn=25+5;
int a[maxn];
int n;
int cal(int x)  //算x的二进制表示中有多少个1.
{
int res = 0;
while(x){
if(x&1) res++;
x >>= 1;
}
return res;
}
void solve()
{
while(~scanf("%d",&n)){
string s; Fill(a,0);
for(int i=0;i<n;i++){
cin >> s;
for(int j=0;j<s.length();j++) a[i] ^= (1<<(s[j]-'A'));
//printf("%d\n",a[i]);
}
//mp[x]表示的是异或值为x的并且个数尽量多组成的. 也就是i中1多的.
map<int ,int >mp;
int n1 = n/2,n2 = n-n1;
for(int i=0;i<(1<<n1);i++){
int tmp = 0;
for(int j=0;j<n1;j++){
if(i & (1<<j))
tmp ^= a[j];
}
if(!mp[tmp] || cal(mp[tmp]) < cal(i)) mp[tmp] = i;
}  //分别枚举

int res = 0;
for(int i=0;i<(1<<n2);i++){
int tmp = 0;
for(int j=0;j<n2;j++){
if(i & (1<<j))
tmp ^= a[j+n1];   //注意是第二部分的.
}
if(mp[tmp] && cal(res) < (cal(mp[tmp]) + cal(i)))
res = (i<<n1) ^ mp[tmp];
//为n1腾出空间, 所以是左移n1位!!!
}

int cnt = cal(res);
printf("%d\n",cnt);
int ans = 0;
for(int i=0;i<n;i++) {
if(res & (1<<i)){  //求出用了哪些串
ans++;
printf("%d%c",i+1,ans == cnt ? '\n':' ');
}
}
printf("\n");
}
}


//重要的是学到中途相遇法的思想!!!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: