题目链接:
题目大意:
给n个大写字母组成的字符串,选择尽量多的串,使得每个大写字母都能出现偶数次。
思路:
一看到Time limit: 18.000 seconds, 很high地无任何优化直接暴力写了一个,9s多过了,估计是自己有史以来耗时最久的一次AC
然后想着怎样优化一下,发现所有字母出现的次数可以用二进制来表示,0表示偶数,1表示奇数。这样的话,把所有选择的字符串状态进行抑或运算一次,结果为0就表示全部是偶数。
这样就从9s降到了1.692s
《竞赛指南》上介绍了效率更高的“中途相遇法”: 把字符串分为2部分, 首先计算前n/2个字符串的所有组合得到的XOR 值,保存在因设map中,然后在枚举后n/2个字符,找和前面一样的值。
// uva 1326 Jurassic Remains // 直接位运算压缩 #include<cstdio> #include<cstring> #include<iostream> #include<cctype> using namespace std; int n; char str[30]; int st[30]; bool vis[30]; int dfs(int cur, int cnt, int sta){ if(cur==n){ if(!sta) return cnt; return -1; } if(cur<n){ vis[cur] = true; int res = dfs(cur+1, cnt+1, sta^st[cur]); if(res != -1) return res; vis[cur] = false; res = dfs(cur+1, cnt, sta); if(res != -1) return res; } } int main(){ while(~scanf("%d", &n)){ memset(st, 0, sizeof(st)); for(int i=0; i<n; ++i){ scanf("%s", str); for(int j=0; str[j]; ++j){ st[i] ^= (1<<(str[j]-'A')); } } memset(vis, 0, sizeof(vis)); printf("%d\n", dfs(0, 0, 0)); bool first=true; for(int i=0; i<n; ++i)if(vis[i]){ first ? first=false : putchar(' '); printf("%d", i+1); } putchar('\n'); } return 0; }
代码2:中途相遇法(递归版本):
#include<cstdio> #include<cstring> #include<map> #include<iostream> using namespace std; const int MAXN = 30; int n, vis; int st[MAXN]; char str[MAXN]; map<int, int>table; map<int, int>::iterator it; int ansCnt, ansVis; inline int bitCount(int x){ int cnt = 0; while(x>0){ if(x&1) ++cnt; x >>= 1; } return cnt; } void dfs1(int cur, int n, int vis, int sta){ it = table.find(sta); if(it != table.end()){ if(bitCount(it->second) < bitCount(vis)){ it->second = vis; } }else{ table[sta] = vis; } if(cur < n){ dfs1(cur+1, n, vis|(1<<cur), sta^st[cur]); dfs1(cur+1, n, vis, sta); } } void dfs2(int cur, int n, int vis, int sta){ it = table.find(sta); if(it != table.end()){ int cnt = bitCount(vis+it->second); if(cnt > ansCnt){ ansCnt = cnt; ansVis = vis+table[sta]; } } if(cur < n){ dfs2(cur+1, n, vis|(1<<cur),sta^st[cur]); dfs2(cur+1, n, vis, sta); } } int main(){ int i,j; while(~scanf("%d", &n)){ memset(st, 0, sizeof(st)); table.clear(); for(i=0; i<n; ++i){ scanf("%s", str); for(j=0; str[j]; ++j){ st[i] ^= (1<<(str[j]-'A')); } } dfs1(0, (n>>1), 0, 0); ansCnt=0, ansVis=0; dfs2(n/2, n, 0, 0); printf("%d\n", ansCnt); bool first=true; for(i=0; i<n; ++i)if((ansVis>>i)&1){ first ? first=false : putchar(' '); printf("%d", i+1); } putchar('\n'); } return 0; }
版本3中途相遇法(直接枚举二进制的状态而不用递归):
#include<cstdio> #include<cstring> #include<map> #include<iostream> using namespace std; const int MAXN = 30; int n, vis; int st[MAXN]; char str[MAXN]; map<int, int>table; map<int, int>::iterator it; int ansCnt, ansVis; inline int bitCount(int x){ int cnt = 0; while(x>0){ if(x&1) ++cnt;x >>= 1; } return cnt; } int main(){ int i,j; while(~scanf("%d%*c", &n)){ memset(st, 0, sizeof(st)); table.clear(); for(i=0; i<n; ++i){ gets(str); for(j=0; str[j]; ++j){ st[i] ^= (1<<(str[j]-'A')); } } // 枚举前n/2个所有组合状态 int end = (1<<(n>>1)); for(i=0; i<end; ++i){ int sta = 0; for(j=0; j<(n>>1); ++j)if(i & (1<<j)){ sta ^= st[j]; } it = table.find(sta); if(it != table.end()){ if(bitCount(it->second) < bitCount(i)){ it->second = i; } }else{ table[sta] = i; } } ansCnt=0, ansVis=0; end = (1<<(n-n/2)); for(i=0; i<end; ++i){ int sta = 0; for(j=(n>>1); j<n; ++j)if(i & (1<<(j-(n>>1)))){ sta ^= st[j]; } it = table.find(sta); if(it != table.end()){ int vis = i<<(n>>1); int cnt = bitCount(vis+it->second); if(cnt > ansCnt){ ansCnt = cnt; ansVis = vis+it->second; } } } printf("%d\n", ansCnt); bool first=true; for(i=0; i<n; ++i)if((ansVis>>i)&1){ first ? first=false : putchar(' '); printf("%d", i+1); } putchar('\n'); } return 0; }