=== ===
这里放传送门
=== ===
题解
判断无解很好判断,就是这个串里面出现次数为奇数的字符只能有至多一个。这道题的关键是发现对于一个能够变成回文串的串,通过交换操作把它变成它的反串的过程中一定能够找到一种方案让它经过回文的状态,并且回文前和回文后交换的情况是对称的,所以设把这个串通过交换变成反串的最小代价为k,那么ans就是k/2。
要计算把一个串通过类似冒泡排序的方式变成另一个串的交换次数是一个经典问题,就是要求出原串中每一个字符在目标串中的对应位置,求出的序列rk[i]=v表示的含义就是应该在第i个的字符现在在第v个,需要把整个序列变成rk[i]=i。那么这就是序列的逆序对数。因为它每做一次有用的交换必然会减少一个逆序对。
还有一个注意的问题就是因为要计算最小交换次数,所以在用原串和反串的字符一一对应的时候要保证相同字母相对顺序不变,因为交换相同的字符是没有意义的。比如样例的 mamad 的反串是 damam ,对应数组是[5,2,1,4,3]而不是[5,4,3,2,1]。
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,pre[8010],nxt[8010],cnt[30],m[8010],tot,k[8010];
char c[8010];
bool can,ext[8010];
void merge(int l,int r){
int i,j,t;
if (l==r) return;
int mid=(l+r)>>1;
merge(l,mid);
merge(mid+1,r);
i=l;j=mid+1;t=l;
while ((i<=mid)&&(j<=r))
if (m[i]<=m[j]){
k[t]=m[i];
++i;++t;
}
else{
k[t]=m[j];
tot+=mid-i+1;
++j;++t;
}
while (i<=mid){
k[t]=m[i];
++t;++i;
}
while (j<=r){
k[t]=m[j];
++t;++j;
}
for (i=l;i<=r;i++){
m[i]=k[i];
}
}
int main(){
memset(ext,false,sizeof(ext));
scanf("%d\n",&n);
for (int i=0;i<n;i++){
c[i]=getchar();
cnt[c[i]-'a']++;
}
can=true;
for (int i=0;i<26;i++)
if (cnt[i]&1==1)
if (can==true)
can=false;//如果是第一次检测到奇数个的字母,那么修改标记
else{//否则输出无解信息直接退出。
printf("Impossible\n");
return 0;
}
memset(cnt,-1,sizeof(cnt));
for (int i=n-1;i>=0;i--){
nxt[i]=cnt[c[i]-'a'];
cnt[c[i]-'a']=i;
}
memset(cnt,-1,sizeof(cnt));
for (int i=0;i<n;i++){
pre[i]=cnt[c[i]-'a'];
cnt[c[i]-'a']=i;
}
for (int i=0;i<n;i++)
if(ext[i]==false){
int v=i;
int p=cnt[c[v]-'a'];//求当前字符最后一个出现的位置
while (p!=-1){
m[n-v-1]=p;
ext[v]=true;
p=pre[p];
v=nxt[v];//移动指针
}
}
tot=0;
merge(0,n-1);
printf("%d\n",tot/2);
return 0;
}
偏偏在最后出现的补充说明
在做对应数组的时候一定要搞清楚求出来的数组元素是表示什么含义,例如“第i个是谁”和“i是第几个”这样非常容易弄混的数组一定要分清楚啊