题意:判断一个字符串是否能转化成回文串,如果可以就统计转化成回文串所需的最少交换次数。
贪心,思路就是先将串两端固定(对应字母移至对应位置),依次向中间夹逼,因为只有依次将字母先放到两端,才可以减少剩下字母交换的次数,先把哪对字母放到两端都可以,因为可以证明出无论移动哪对字母只要是每次都将其放到两端其最后交换次数是一样的。
由题意不难判断如果各个字母出现次数均为偶数,那他一定可以转换成回文串,如果有奇数次的字母出现,其有且至多出现一个这样的字母。如果这个字母出现的次数为一次,我们可以在交换时“忽略”它,即:在从前往后找对应匹配字母使其移动后占据子串两端时忽略这个特殊的字母,只要遇到它就将其向后移动,这样最终就可以将它移至中间位置,如果这个字母出现的次数大于一次(3,5,7....),我们可以将最中间的那个字母转化成其他符号,则剩下的这个字母为偶数个,再按一个奇数字母的做法做即可。
代码如下:
#include<iostream> #include<cstdlib> #include<cstring> #include<cstdio> #include<cmath> #include<cstdlib> using namespace std; int check(char *a, int len) // 检测是不是回文串 { int b[30], flag = 0, fct; memset(b, 0, sizeof(b)); for(int i = 0; i < len; i++) ++b[a[i] - 'a']; for(int i = 0; i < 26; i++) if(b[i] % 2) { fct = i; ++flag; } if(flag > 1) return 0; else if(flag == 1) // 是回文串且具有字母出现奇数次,则返回该奇数次字母对应的数字 return fct + 1; return 100; // 如果回文串中的字母均出现偶数次,则直接返回一个较大的正数 } int main() { #ifdef test freopen("in.txt", "r", stdin); #endif int t, len, flag; char a[102]; scanf("%d", &t); getchar(); while(t--) { int cct = 0, i; scanf("%s", a); len = strlen(a); flag = check(a, len); if(flag) { int j, num = 0; char c = '*'; if(flag < 100) { c = 'a' + flag - 1; for(i = 0; i < len; i++) if(a[i] == c) ++num; if(num > 1) // 如果奇数字母出现的次数大于一次,则需将其转换成“奇数字母出现一次”的情况(将奇数字母最中间的那个字母转化成‘*’号) { for(int i = 0, knum = 0; i < len; i++) if(a[i] == c) { if(knum == num / 2) { a[i] = '*'; c = '*'; break; }++knum; } } } for(i = 0; i < len / 2; i++) { if(a[i] == c) // 遇到奇数字母就将其后移 { char t = a[i]; a[i] = a[i + 1]; a[i + 1] = t; ++cct; } if(a[i] != a[len - 1 - i]) // 对应位置对应字母,从两端开始占据依次向中间夹逼 { for(j = len - 1 - i; j >= i + 1; j--) if(a[j] == a[i]) break; for(int k = j; k < len - 1 - i; k++) { char t = a[k]; a[k] = a[k + 1]; a[k + 1] = t; ++cct; } } } printf("%d\n", cct); } else printf("Impossible\n"); } return 0; }