2019年浙江省赛Strings in the Pocket

给定两个字符串s,t,问通过一次倒置s中的子串能否变为t,若不能输出0,若能输出方案次数。
首先通过一波分析可得大体上可分为两种情况,情况1:两子串不相同,情况2:两子串相同。
针对这两种情况展开分析:
情况1:
两子串不同时,设s=s1+s2+s3,t=t1+t2+t3,只有当s1t1&&s3t3且将s2倒置后t1后才有方案能满足题意(s1,s3,t1,t3均可为空),例:s=abcd,t=acbd时答案为1(只有通过倒置s中的bc,才能使st),但是当s=adbcda,t=adcbda时,答案为3(倒置s字符串区间(2,3),(1,4),(0,5)的字符后均有st)
情况2:
两子串相同时,可倒置s中的回文串得到答案(注意,一个字符串也可倒置),划重点,此时将会用到一个非常牛逼的算–马拉车,马拉车是一个用来计算每个字符的回文半径的算法(比如abcba,这里可以看出两个a的回文半径都为1,b的回文半径都为2,c的回文半径为3),算法实现时添加了字符串没有用到的字符(任何未用到的字符都行,我选的是’#’),将该字符插入到两个字符之间,第一个和最后一个也都插上,这样就可以避免回文串字符数为偶数时(如aa)忽略掉部分情况,(aa,两个a的半径都为1,此时便会忽略掉aa也可直接倒置的情况,但加入’#‘后,字符串变为"#a#a#",回文半径为1,2,3,2,1,由于还要去掉半径中的’#’,故真实半径就变为0,1,1,1,0,此时答案就为3)
啥也不说了,马拉车牛逼!上代码

//ZOJ--4110
#include<bits/stdc++.h>
using namespace std;
const int N = 5 * 1e6;
char s[N];
char t[N];
char ns[N];                 //保存新的字符串
int T;
int len;
int p[N];
void init() {
	ns[0] = '$';            //为了避免后续使用时超出范围,故在第一位插入另一个不可能出现的字符,减少运算量
	ns[1] = '#';            //每两位字符之间插入'#'
	int j = 2;
	for (int i = 0; i < len; i++) {
		ns[j++] = s[i];
		ns[j++] = '#';
	}
	ns[j] = '\0';         
}
void manacher() {
	init();
	int pid, mx = 0;        //pid记录前一个回文串的位置,mx记录前一个回文串最右边的边界
	for (int i = 1; i <= 2 * len + 1; i++) {    //插入之后字符串长度变为2*len+1(不包括'\0')
		if (i <= mx)       //若当前位置未超出上个回文串最右边的位置,则说明当前字符在上个回文串中
			p[i] = min(p[2 * pid - i], mx - i); //若对称位置的字符串回文半径+当前位置i<=mx说明当前字符回文半径与对称位置的回文半径相同,否则等于mx-i,因为i还处于上个回文串中
		else
			p[i] = 1; //否则先设置其回文半径为1
		while (ns[i + p[i]] == ns[i - p[i]])p[i]++; //扩展p[i],此时可能出现下标越界的情况,所以需要设置ns[0]='$'
		if (i + p[i] > mx) { //若当前字符右界超过上个回文串右界,则更新pid与mx
			pid = i;
			mx = i + p[i];
		}
	}
}
int main() {
	scanf("%d", &T);
	while (T--) {
		scanf("%s%s", s, t);
		int l1 = strlen(s), l2 = strlen(t);
		if (l1 != l2) {  //若两字符串长度不等,则答案肯定为0
			printf("0\n");
			continue;
		}
		len = l1;
		int l = 0, r = len - 1;
		while (l < len) {
			if (s[l] != t[l])break;
			l++;
		}
		while (r >= 0) {
			if (s[r] != t[r])break;
			r--;
		}
		if (l == len) {  //若l==len,说s==t,则采用马拉车算法处理
			manacher();
			long long ans = 0;  //一定要用long long,要不然会爆炸的
			for (int i = 1; i <= 2 * len + 1; i++)ans += p[i] / 2;  //每个符号的回文半径中包括了'#',则要/2处理
			printf("%lld\n", ans);
		}
		else {  //l!=len说明s!=t
			int tt = r - l + 1; //找出不等的长度
			int i;
			for (i = 0; i < tt; i++) {
				if (s[l + i] != t[r - i])break; //判断倒置后将s的[l,r]倒置后是否等于t,不等答案则为0
			}
			if (i < tt)
				printf("0\n");
			else {
				long long ans = 1;
				l--;
				r++;
				while (l >= 0 && r <= len - 1) {  //找出两边可扩展的个数,每扩展一个ans++
					if (s[l] != s[r])break;
					l--;
					r++;
					ans++;
				}
				printf("%lld\n", ans);
			}
		}
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值