今天来水一发题解
1.题意:将两个相同的字符串s拼接形成ss串,又在其中加某个字母x使其变成输入的s’串,求原s串
2.性质:
-
1 首先发现,要是能找到合法的s,|s’|必须为奇数(依据此性质可以特判 s’ 是否合法 )
2 而且去掉x后从中间分开左右子串应是相等的,这是判断是否合法的又一方法。
3 又通过
惊人的注意力观察发现,所插入的字母x只有可能出现在s’的前半段、中间、后半段。也就是意味着总有一半是原来的s串。(可用作判断与输出)
3.做法:
①hashing做法( O ( n 2 ) O(n^2) O(n2))。
将这个多余字母所在的位置从0~n-1枚举过去,看是否使剩的字符串符合要求。
首先,枚举到某个位置i,判断i在前半段or后半段or中间。然后根据哈希公式( h a s h [ x , y ] = h a s h [ 0 , y ] − h a s h [ x − 1 ] ∗ p y − x + 1 ( x < y ) hash[x,y]=hash[0,y]-hash[x-1]*p^{y-x+1}(x<y) hash[x,y]=hash[0,y]−hash[x−1]∗py−x+1(x<y) 及 h a s h [ a , b ] + h a s h [ c , d ] = h a s h [ a , b ] ∗ p d − c + 1 + h a s h [ c , d ] ( a < b < c < d ) hash[a,b]+hash[c,d]=hash[a,b]*p^{d-c+1}+hash[c,d](a<b<c<d) hash[a,b]+hash[c,d]=hash[a,b]∗pd−c+1+hash[c,d](a<b<c<d))(hash[n,m]表示s’下标从n~m的子串的hash值)得到删去s’[i]后,从中间分开左右子串的hash值是否相同推得是否合法。
代码(节选自北岭山脚鼠鼠的博文):
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
typedef unsigned long long ull;
const int pp=13331,N=2e6+10;
char a[N];
ull p[N];
ull last;
ull s[N];
int n;
ull get(ull a[],int l,int r)//用于获取hash值的函数
{
return a[r]-a[l-1]*p[r-l+1];
}
int main()
{
cin>>n;
scanf("%s",a+1);
if(n%2==0)//特判|s’|是否为奇数
{
printf("NOT POSSIBLE\n");
return 0;
}
p[0]=1;
for(int i=1;i<=n;i++)
{
s[i]=s[i-1]*pp+a[i];
p[i]=p[i-1]*pp;
}
int sum=0;
int ans;
for(int i=1;i<=n;i++)
{
if(i==n/2+1)//当i在中间
{
if(get(s,1,n/2)!=last&&get(s,1,n/2)==get(s,n/2+2,n))
{
last=get(s,1,n/2);
sum++;
ans=i;
}
}else if(i<=n/2)//i在左边
{
if(get(s,n/2+2,n)!=last&&get(s,n/2+2,n)==get(s,1,i-1)*p[n/2+1-i]+get(s,i+1,n/2+1))
{
last=get(s,n/2+2,n);
sum++;
ans=i;
}
}else//i在右边
{
if(get(s,1,n/2)!=last&&get(s,1,n/2)==get(s,n/2+1,i-1)*p[n-i]+get(s,i+1,n))
{
last=get(s,1,n/2);
sum++;
ans=i;
}
}
if(sum>1)
break;
}
if(sum>1)//如果有多个解
puts("NOT UNIQUE");
else if(sum==0)//如果没有解
puts("NOT POSSIBLE");
else{//一个解时
if(ans>=n/2+1)//x在右
{
for(int i=1;i<=n/2;i++)
printf("%c",a[i]);
}else//x在左
for(int i=n/2+2;i<=n;i++)
printf("%c",a[i]);
}
return 0;
}
②非hashing的O(n)复杂度做法:
由性质3可得x会出现在前半段or中间or后半段,此处中间会并入两边不需特判。
首先,检查x是否在后半段:先将字符串分成两段,一段为s’[0] ~ s’[m-1]( m = n / 2 m=n/2 m=n/2),the other is s’[m] ~ s’[n-1]。i,j指针初始分别指向s’[0],s’[m],将持续后移,判断s’[i]==s’[j],为真j后移一位,否则不动。(边界i<n,j<m)这样做如果后半段有x,就会使s’[0] ~ s’[m-1]为原串s,而s’[m]~s’[n-1]里就包含了原串s,所以最后j会等于m。
同理,以此思路检查前半段,得出结论。
代码:
#include<bits/stdc++.h>
using namespace std;
int n;
string s;
bool flag1,flag2;
int main(){
cin>>n>>s;
if(n%2==0){//特判|s’|是否为奇数
cout<<"NOT POSSIBLE";
return 0;
}
int m=n/2;
//检查后半段是否有x
string s1=s.substr(0,m);
int j=0;
for(int i=m;i<n&&j<m;i++){
if(s1[j]==s[i]) j++;
}
if(j==m) flag1=true;
//检查前半段是否有x
string s2=s.substr(n-m,m);
j=0;
for(int i=0;i<n-m&&j<m;i++){
if(s2[j]==s[i]) j++;
}
if(j==m) flag2=true;
//输出
if(!flag1&&!flag2){//前后无合法x
cout<<"NOT POSSIBLE";
}
else if(flag1&&flag2&&s1!=s2){//前后都有合法x(当x不同,s相同时,算同一种)
cout<<"NOT UNIQUE";
}
else{//一个解时
if(flag1){//x在后半段
cout<<s1;
}
else{//x在前半段
cout<<s2;
}
}
return 0;
}
4.两做法优劣势对比(仅个人看法):
思维复杂度 | 时间复杂度 | 代码复杂度 | 其他 | |
---|---|---|---|---|
做法① | 一般 | $ O(n^2) $ | 复杂 | 因当x不同,s相同时,算同一种。须要特判 |
做法② | 较难 | $ O(n) $ | 简单 | 同上 |