题目传送门:~~~
题目大意
给定一个字符串 s 和一个指针的位置 pos,该指针指向字符串。
你可以进行四个操纵:
- left : 指针向左移动
- right:指针向右移动
- up:指针指向的字母的ASCLL码加一,特别得,z 变为 a
- down:指针指向的字母的ASCLL码减一,特别得,a 变为 z
问:最少多少次操作以后,字符串 s 可以变成回文字符串(左右对称的字符串,eg:abccba)
分析
根据分治的思想,这个题目可以分成两个子问题:
- 使得 s[i] = s[n-i-1] (使得下标从0开始) 全部成立
- 指针的移动轨迹
也就是说,指针移动和改变字母分开解决,移动需要的操作数和改变字母需要的操作数是完全独立的,我们一个一个解决就OK。
step 1 :改变字母
要使 s[i] = s[n-i-1],有两种方法:
要么,s[i] 直接变成 s[n-i-1]。 eg:a 变成 d
要么,s[i] 变成 z ,再从z 到 a,最后 从 a 变成 s[n-i-1] 。或者是 s[i] 变成 a ,再从a 到 z,最后 从 a 变成 s[n-i-1] 。 eg:a 变成 x 或者 x 变成 a
需要的操作次数为 sum i = min( |s[i] - s[n-i-1]|,26 - |s[i] - s[n-i-1]| ),对所有的sumi求和就OK,注意 i 的取值是 【0,n/2】。
step 2 :指针的移动轨迹
这里进行一波思维转化, pos = x 和 pos = n - x + 1 是指到的字母是对称的,那也就是说,给出的pos在区间(0,n/2)和(n/2+1, n)是等价的。为了简化问题,当给出的 pos 在区间(n/2+1, n)时,我们把 pos = x ,转化成 pos = n - x + 1 ,不用再分两种情况去讨论。
给出一个便于理解的图:
两个箭头关于黄线对称,红色箭头移动时,蓝色箭头也同步移动,所以我们可以认位,红色箭头与蓝色箭头等价。上图中,n = 8,为偶数,黄线两侧的字母数目相同。但,如果 n 为奇数呢?
eg:n = 7,s = asdwdca
我们发现,我们找不到一条线,把 s 分成字母数目相同的两部分,我们应该想办法,使 s 的长度加 1
显然是可以的,令 s * = asdwwdca, s* 就是一个满足的字符串,我们把 s 最中间的字母 x(x = s[中间])插入到 s 的中间就可以得到一个满足要求的字符串 s *
这样我们就可以只讨论,n 为偶数情况,减少代码量
再说说我们应该如何移动指针,我们在上图中做一下手脚:
直接给出最优的路线(1 -> 0 -> 1 -> 2 -> 3),我们在考虑路径时,只需要考虑黄线左侧的一半。在上例中每个字母都需要进行变化,故而需要遍历左侧的全部字母,显然我们要先走到 0 号位,再到 3 号位(如果先到 3 再到 0 的走法,步数会多)。
这里我们定义:从蓝色箭头处到右边界的步数为 vis1,从蓝色箭头处到黄线处的步数为 vis2。总步数为 sum,那么,sum = min(2 * vis1 + vis2, 2 * vis2 + vis1)。
我们再思考一个问题,我们一定要走到边界才停止嘛?
看下边的图:
很明显,我们只需要走到最后一个字母不同处就OK,没必要走到边界,所以,这里我们要重新一定一下vis1 和 vis2:
从蓝色箭头处到右侧 最后一个字母不同处的步数为 vis1,从蓝色箭头处到 左侧最后一个字母不同处的步数为 vis2。总步数为 sum,那么,sum = min(2 * vis1 + vis2, 2 * vis2 + vis1)。
代码:
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
int n, pos;
cin >> n >> pos;
string s;
cin >> s;
if(pos >= n / 2) {
pos = n - pos + 1; // 指针转化
}
if(n & 1){
char a = s[n/2];
s.insert(n/2, 1, a); // 在 n/2 前以为插入一个 s[n/2]
n++;
}
int res = 0;
for(int i = 0; i < n / 2; i++){ // 统计sum,并求和
int num = abs(s[i] - s[n-i-1]);
res += min(num, 26 - num);
}
int vis_1 = 0; pos--;
for(int i = 1; pos - i >= 0; i++){ // 统计指针左侧要走的步数
if(s[pos-i] != s[n - (pos-i) - 1]){ // 如果不等就更新vis_1;
vis_1 = i;
}
}
int vis_2 = 0;
for(int i = 1; pos + i < n / 2; i++){ // 统计指针右侧要走的步数
if(s[pos+i] != s[n - (pos+i) - 1]){
vis_2 = i;
}
}
// 计算总步数
if(vis_1 == 0 && vis_2 != 0) res += vis_2;
if(vis_1 != 0 && vis_2 == 0) res += vis_1;
if(vis_1 != 0 && vis_2 != 0) res += vis_1 + vis_2 + min(vis_1, vis_2);
cout << res << endl;
return 0;
}
这个题目的重点在于分治和转化,大大减小了代码量,时间复杂度o(n);