CodeForces - 486C(思维 + 字符串模拟)

题目传送门:~~~

题目大意

给定一个字符串 s 和一个指针的位置 pos,该指针指向字符串。
你可以进行四个操纵:

  • left : 指针向左移动
  • right:指针向右移动
  • up:指针指向的字母的ASCLL码加一,特别得,z 变为 a
  • down:指针指向的字母的ASCLL码减一,特别得,a 变为 z

问:最少多少次操作以后,字符串 s 可以变成回文字符串(左右对称的字符串,eg:abccba)

分析

根据分治的思想,这个题目可以分成两个子问题:

  1. 使得 s[i] = s[n-i-1] (使得下标从0开始) 全部成立
  2. 指针的移动轨迹

也就是说,指针移动和改变字母分开解决,移动需要的操作数和改变字母需要的操作数是完全独立的,我们一个一个解决就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);

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值