Bootstrap

C1459. 「一本通 2.1 练习 3」Friends题解

今天来水一发题解

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[x1]pyx+1x<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]pdc+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) $简单同上

点个赞再走吧!QAQ

;