给定两个字符串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;
}