题意:选出一段子序列并将其反转让整串字符变成回文串
2700: 子串翻转回文串
时间限制: 2 Sec 内存限制: 128 MB
提交: 588 解决: 113
题目描述
给一个串 s=s1s2⋯sn,你可以选定其一个非空子串,然后将该子串翻转。具体来说,若选定的子串区间为 [l,r](1≤l≤r≤n),则翻转后该串变为 s1s2⋯sl−1srsr−1⋯slsr+1⋯sn。
请你回答仅通过一次上述操作后,s 是否能变成回文串。串 s 是回文串,当且仅当它从左至右读出与从右至左读出完全相同,即 s1s2⋯sn=snsn−1⋯s1
输入
注意:本题包含多组测试数据。
第一行包含一个整数 T(1≤T≤5×105),表示数据组数。
接下来的 T 行,每行包含一个仅由英文小写字母组成的字符串 s,含义见题目描述,且串长 ∣s∣ 满足1≤∣s∣≤5×105。
保证字符串总长 ∑∣s∣不超过 5×105。
输出
对于每组测试数据,输出一行一个字符串,若仅通过一次操作后 s 能变成回文串,则输出 Yes,否则输出 No,大小写不敏感。
样例输入 Copy
4 abba bacad abacbaa aabadcdca
样例输出 Copy
Yes No Yes Yes
提示
第一组数据中,abba 翻转带下划线的子串得到回文串 abba。
第二组数据中,无论如何翻转子串,都不能得到回文串。
第三组数据中, abacbaa翻转带下划线的子串得到回文串 aabcbaa。
第四组数据中, aabadcdca翻转带下划线的子串得到回文串 acdabadca。
来源/分类
字符串哈希:将字符串变成x进制数
对公式的理解:
举个十进制数的例子:123456
h[1]=1;
h[2]=1*10+2=12;
h[3]=12*10+3=123;
h[4]=123*10+4=1234;
.........
h[i]=h[i-1]*x+a[i];
h[i]代表的恰巧是整个数的前缀
用p[i]表示进制数的 i 次方;
想要单取出3-5的子串的值345
步骤;
1,先取出前缀12345(q[5])
2,再减去12000:(h[2]*1000);
取子串哈希值的公式为:h[r]-h[l-1] *(r-l+1);
本题思路:
去掉前缀后缀相同的部分,剩余的前缀或后缀必定要翻转一次
判法:正反跑一次哈希(下标不变权值翻转)
--------------------------------------> 正向权值整串s1
----------> 正向权值翻转部分s2
<---------- 反向权值翻转部分s3
<-------------------------------------- 正向权值整串s4
判法:比较翻转前与翻转后的整串的哈希制是否一样
代码:
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
const long long N=1e6+10;
const long long mod=1e9+7;
char a[N];
long long h1[N],p[N],h2[N],x=133331,L;
void init()
{
long long i,j;/*尽量两边多算一位*/
p[0]=1,h1[0]=a[0];
for(i=1; i<=L+1; i++)
{
h1[i]=(h1[i-1]*x%mod+a[i])%mod;
p[i]=(p[i-1]*x)%mod;
}
h2[L+1]=0;
h2[L]=a[L];
for(i=L-1; i>=0; i--)/*反向哈希*/
h2[i]=(h2[i+1]*x+a[i])%mod;
}
long long get1(int l,int r)
{
if(l==0) return h1[r];
return ((h1[r]%mod-h1[l-1]*p[r-l+1]%mod)+mod)%mod;/*注意负数取模*/
}
long long get2(int l,int r)
{
return (h2[l]%mod-h2[r+1]*p[r-l+1]%mod+mod)%mod;
}
int pan(int l,int r)
{
int hash_s1=(get1(0,L)-(get1(l,r)*p[L-r])%mod+(get2(l,r)*p[L-r])%mod+mod)%mod;
/*从s1中取出s2放入s3所得出整串的哈希值*/
int hash_s2=(get2(0,L)-(get2(l,r)*p[l])%mod+(get1(l,r)*p[l])%mod+mod)%mod;
/*从s4中取出s3放入s2所得出整串的哈希值*/
return hash_s1==hash_s2;/*比对*/
}
int sove()
{
int pos=-1;
init();
for(int i=0; i<=L; i++)
if(a[i]!=a[L-i])
{
pos=i;
break;
}
if (pos==-1) return 1;
for(int i=pos+1; i<=0+L-pos; i++)/*枚举翻转前后缀*/
if(pan(pos,i)||pan(i,L-pos))
return 1;
return 0;
}
int main()
{
int w;
scanf("%d",&w);
while(w--)
{
scanf("%s",a);
L=strlen(a)-1;
if(sove()) printf("Yes\n");
else printf("No\n");
}
return 0;
}