题目
题解
Impossible
的情况:当字符串长度为偶数时,不能存在出现次数为奇数次的字符,而且若出现则必然出偶数个“出现次数为奇数次”的字符,即出现次数为奇数次的字符的个数>1
;当字符串长度为奇数时,必然存在一个字符出现次数为奇数。因此两种情况可以统一为:若出现次数为奇数次的字符的个数>1
,则Impossible
。- 将字符串分为两半,对于在左半边遍历到的字符,我们去右半边从右向左找(右半边遍历的起点为左侧当前字符关于中心位置对称过去的位置,右半边遍历的终点为左侧当前字符的后一个位置,即无法到达左侧当前字符),找到第一个与左边遍历的当前字符一样的右边字符,我们就让找到一致字符位置到右半边遍历的起点处从左至右顺次交换,统计交换次数。
- 特殊情况,当存在孤立字符在左半边时(孤立字符就是在字符串长度为奇数时存在的出现次数为奇数的字符),我们跳过它,先不让它进行交换,而是等全部其他的字符都完成了交换后,再让它直接交换到中心即可,这样是防止假如它提前移动到中心,之后的一些交换操作若跨越了中心,那么必然会增加交换的次数。跳过它之后,我们右半边遍历的起点还要保持为上一个位置,也就是说从遇到孤立字符之后遍历的左半边字符对应于右半边查找的起点不再是关于中心对称的了,而是关于中心的对称位置再
+1
(不理解可见下图),之后的操作还是与“2”中描述的是一样的。
这是上面"3"所描述的问题:
当遇到孤立字符p
,p
对应的搜索应当从右侧的d
开始,但是p
不进行搜索,因此左侧的d
就还是从右侧的d
开始搜起。
其他大佬模拟的一个过程,这是对应于上面“2”的描述:
对应于上面“3”的描述:
代码
#include<bits/stdc++.h>
using namespace std;
int n, ans, cnt[26], ccnt, flag; // flag用于控制是否遇到过孤立字符了
//int rit; // 另一种方式控制右侧遍历的起点
string s;
int findd(int r, int l, char c) { // 此函数用于从右侧向左侧找到第一个与目标一样的字符,参数名就是其含义
for(int i = r;i > l;i --) if(s[i] == c) return i;
return -1; // 没找到返回-1
}
int change(int r, int l) { // 此函数用于实现交换,将字符串修改成交换后的样子
char c = s[l];
for(int i = l;i < r;i ++) s[i] = s[i+1];
s[r] = c;
return r - l; // 返回交换次数
}
int main()
{
cin>>n>>s;
// 将 Impossible 的情况判出去
for(int i = 0;i < n;i ++) cnt[s[i]-97] ++;
for(int i = 0;i < 26;i ++) ccnt += (cnt[i]&1) ;
if(ccnt > 1) {puts("Impossible");return 0;}
int mid = n/2; // 例如:0 0 0 1 0 0 0, 我这里不考虑1位置,左侧只遍历三个0
// rit = n-1; // 初始右侧遍历的起点为n-1
for(int i = 0;i < mid;i ++) {
int pos = findd(n-i-1+flag, i, s[i]); // 之所以要加flag,是因为在没有遇到孤立字符之前,左侧+1右侧就-1,但是遇到孤立字符时,左侧照常+1,而右侧会少一次-1,这个flag就是实现右侧少一次-1的。实在理解不了就看下面的注释代码
// int pos = findd(rit, i, s[i]); // 可以将上面一行换成这个,通过rit控制右侧开始遍历的起点
if(pos == -1) ans += mid-i, flag = 1; // 若遇到孤立字符了,标记一下,同时单独加上将孤立字符交换至中间的操作次数
else ans += change(n-i-1+flag, pos); // 这里的+flag同上面的+flag
// else ans += change(rit--, pos); // 可以将上面一行换成这个,若不是孤立字符,则右侧起点位置--,若是鼓励字符,起点不变
}
cout << ans << endl;
return 0;
}
更新于 2022.4.2 发现了一个更容易理解和实现的代码。
#include<bits/stdc++.h>
using namespace std;
int n, ans;
int flag; //判断是否已经有一个单独的奇个数的字符了
string s;
int main()
{
cin >> n >> s;
int m = n - 1; // 指向尾部的指针
for (int i = 0;i < m;i ++) {
for (int j = m;j >= i;j --) {
if (i == j) { // 没找到相同的
if (n % 2 == 0 || flag) return puts ("Impossible"), 0; //不可能的两种情况
flag = 1; //找到一个字符出现的次数为奇数
ans += n / 2 - i; //将次字符交换到中间位置的次数,但本质上位置是不动的
} else if (s[i] == s[j]) { // 找到相同的
for (int k = j;k < m;k ++)
swap (s[k], s[k+1]); //交换相邻两个位置的字符
ans += m - j; // 这个要在m--之前!
m --;
break;
}
}
}
cout << ans << endl;
return 0;
}
吐槽自己
题不会就罢了,看别人思路写出来了,但是他妈的debugde了一个多小时,最后发现是错把a
的ASSCII码当成96
了,导致一开始判断Impossible
就错了。服了。