问题描述

  回文串,是一种特殊的字符串,它从左往右读和从右往左读是一样的。小龙龙认为回文串才是完美的。现在给你一个串,它不一定是回文的,请你计算最少的交换次数使得该串变成一个完美的回文串。
  交换的定义是:交换两个相邻的字符
  例如mamad
  第一次交换 ad : mamda
  第二次交换 md : madma
  第三次交换 ma : madam (回文!完美!)

输入格式

  第一行是一个整数N,表示接下来的字符串的长度(N <= 8000)
  第二行是一个字符串,长度为N.只包含小写字母

输出格式

  如果可能,输出最少的交换次数。
  否则输出Impossible

样例输入

       5
       mamad

样例输出

       3

 1,首先讨论Impossible的情况

①当字符串长度N是偶数时,如果存在一个字符的个数是奇数,那么就不可能组成回文串,例如:ababbc

②当字符串长度N是奇数时,如果个数是奇数的字符数大于1,那么就不可能组成回文串,例如:abcdd

综上所述可知只要是字符串中个数是奇数的字符数大于1,那么这个字符串就不可能组成回文串

 2,然后讨论可以组成回文串的情况

当确定字符串可以组成回文串后,要解决的问题就是要如何找到转换到回文串所需的最少交换次数。实际上每一组对称的字符的位置关系个数都是有限的,可以分成如下几种情况来具体分析。

①当字符串长度N是偶数时

<figcaption> A表示当前要移动的字符对,X表示其他字符 </figcaption>

 

 

 

 

 如果以左边的A为标准来移动右边的A,将右边的A移动到左边A在右边的对称位置移动的次数与以右边的A为标准来移动左边的A,将左边的A移动到右边A在左边的对称位置的移动次数是一样的,所以移动次数和移动那一边的字符无关。

对于每一对字符,他们的相对位置关系要么在中间线的两侧,要么在中间线的同侧,经分析可知,在同侧的情况下依旧符合上述结论,移动次数和先移动哪一个字符无关。

接下来要解决的问题是我们在交换移动的时候有可能会破坏先前已经排列好的字符对,我们可以从字符串的左边开始,以每一个字符为基础,然后从其在右侧对称的位置向左遍历查找与他相同的字符,当找到第一个相同的字符后就将这个相同的字符移动到对称位置,这样字符串两端就是已经排列好的字符,移动只会在未排列好的中间位置进行,不会破坏已经排列好的序列。

②当字符串长度N是奇数时

 对于字符数是偶数个的字符对,移动过程和字符串长度为偶数时一样的,要解决的就是字符数为奇数的字符选择哪一个放在中间位置。为了保证不去破坏已排列好的队列,如果此字符的个数为c,那么就在遍历到第(c+1)/2个时将这个放到中间位置。这样就需要引入一个记录遍历次数的变量。我这里是用的另一种方法可以不用记录遍历次数:每当遍历到奇数个的字符时,就查看其对称位置的字符,(1)如果字符相同说明已经对称,不用移动。(2)如果字符不同,就以这个对称位置的字符为基础,来从左边开始找与其相同的字符来进行移动。

例如:ffdejjell

 上面第二行是从左边遍历到d时,可知d是奇数个字符,所以以其右边对称位置的l为基础来进行移动,形成对称对。后面同理。

以上就是这道题的基本思路,下面是我的代码,可以参考一下

#include<bits/stdc++.h>
using namespace std;

int cnt=0;//记录移动次数 
	
void Swap_1(char *a,int i,int t,int n)//对偶数个字符进行移动 
{						//i基础位置,t对称位置,n字符个数 
	while(t!=n-i-1)
	{
		swap(a[t],a[t+1]);
		cnt++;
		t++;
	}
}
void Swap_2(char *a,int i,int t,int n)//对奇数个字符进行移动 
{
	while(t!=n-i-1)
	{
		swap(a[t],a[t-1]);
		cnt++;
		t--;
	}
}

int main()
{
	int n;
	scanf("%d",&n);
	getchar();
	char a[n+1];
	gets(a);	
	
	map<char,int>m;//用map来记录每一个字符的出现次数 
	
	int i,t;
	for(i=0;i<n;i++)//记录每一个字符的出现次数 
	{
		m[a[i]]++;
	}
	
	map<char,int>::iterator it;
	char flag;//记录奇数个字符
	
	if(n%2==0)//字符个数是偶数 
	{
		for(it=m.begin();it!=m.end();it++)
		{
			if(it->second%2!=0)//偶数个字符情况下只要是出现有一个字符的个数是奇数,就不可能是回文串 
			{
				printf("Impossible");
				return 0;
			}
		}
		
		for(i=0;;i++)//交换移动 
		{
			for(t=n-i-1;t>i;t--)
			{
				if(a[t]==a[i])
				{
					Swap_1(a,i,t,n);
					break;
				}
			}
			if(t<=i)//当t<=i时,说明已经排列完成了 
			break;
		}
	}
	else//字符个数是奇数 
	{
		int sum=0;//记录奇数个字符的个数 
		for(it=m.begin();it!=m.end();it++)
		{
			if(it->second%2!=0)
			{
				sum++;
				flag=it->first;
			}
		}
		if(sum!=1)
		{
			printf("Impossible");
			return 0;
		}
		else
		{
			for(i=0;;i++)
			{
				if(a[i]!=flag)
				{
					for(t=n-i-1;t>i;t--)
					{
						if(a[t]==a[i])
						{
							Swap_1(a,i,t,n);
							break;
						}
					}
					if(t<=i)
					break;
				}
				else
				{
					if(a[n-i-1]==flag)//如果相同表示已经排列好了,不用移动了 
					continue;//注意这里不是break 
					else
					{
						for(t=i;t<n-i-1;t++)
						{
							if(a[t]==a[n-i-1])
							{
								Swap_2(a,n-i-1,t,n);//这里要注意传参 
								break;
							}
						}
					}
				}
			}
		} 
	}
	
	printf("%d\n",cnt);
	return 0;
}