字符串匹配问题

字符串匹配问题

寻找长度为 n n n 的主串 S S S 中的匹配串 T T T(长 m m m)出现的位置或次数。
字符串哈希是将比较字符串转化为比较字符串的哈希值是否相等。
H ( C ) H(C) H(C) 表示字符串 C C C 的哈希值, H ( C , k ) H(C,k) H(C,k) 表示字符串 C C C 到第 k k k 位的哈希值。

具体流程

取两个合适的互质常数 p , q p,q p,q,对于字符串 C = c 1 c 2 c 3 … c m C=c_1c_2c_3\dots c_m C=c1c2c3cm
H ( C ) = ( c 1 p m − 1    c 2 p m − 2    … c m p 0 ) m o d    q H(C)=(c_1p^{m-1}\;c_2p^{m-2}\;\dots c_mp^0) mod\; q H(C)=(c1pm1c2pm2cmp0)modq。一般可取 q = 2 32 q=2^{32} q=232 2 64 2^{64} 264 自然溢出。
此时比较 S ( n 位 ) S(n位) S(n) C C C 的子串 C ′ = c k + 1    c k + 2    … c k + n C'=c_{k+1}\;c_{k+2}\;\dots c_{k+n} C=ck+1ck+2ck+n 是否匹配,只需比较 H ( S ) H(S) H(S) H ( C ′ ) = H ( C , k + n ) − H ( C , k ) ⋅ p n H(C')=H(C,k+n)-H(C,k)\cdot p^n H(C)=H(C,k+n)H(C,k)pn

正确性

由于取模,不同字符串的哈希值可能相同,但若 q q q 较大,重复的概率较小。可使用双哈希,分别使用 1 0 9 + 7 和 1 0 9 + 9 10^9+7 和 10^9+9 109+7109+9(孪生质数)。

题目

Oulipo

模板题

Code:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<string>
#include<cstring>
using namespace std;
typedef unsigned long long ull;

string s1,s2;
ull pow[1000001];
ull has[1000001];

int main()
{
    pow[0]=1;
    for(int i=1;i<=1000000;i++)pow[i]=pow[i-1]*30;
    int T;
    cin>>T;
    for(;T;T--)
    {
        int ans=0,len1,len2;
        cin>>s1>>s2;
        len1=s1.size();
        len2=s2.size();
        has[0]=0;
        for(int i=0;i<len2;i++)
        {
            has[i+1]=has[i]*30+(ull)(s2[i]-'A'+1);
        }
        ull has1=0;
        for(int i=0;i<len1;i++)
        {
            has1=has1*30+(ull)(s1[i]-'A'+1);
        }
        for(int i=0;i<=len2-len1;i++)
        {
            if(has1==has[i+len1]-has[i]*pow[len1])ans++/*,cout<<i<<"jjj"<<endl*/;
        }
        cout<<ans<<endl;
    }
    return 0;
}

Power String

此题似乎可以暴力过,暴力代码就不放了。
最简单的想法就是取前 1 1 1 位、 2 2 2 位枚举。

Code

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
using namespace std;
typedef unsigned long long ull;
#define b 131

ull power[1000010];
ull has[1000010];

int main()
{
    string s;
    
    power[0]=1;
    for(int i=1;i<=1000000;i++)power[i]=power[i-1]*b;
    
    while(1)
    {
        cin>>s;
        if(s==".")return 0;
        
        int len=s.size();
        has[0]=0;
        for(int i=0;i<len;i++)has[i+1]=b*has[i]+(ull)(s[i]);
        
        // for(int i=1;i<=len;i++)cout<<power[i]<<endl;
        for(int i=1;i<=len;i++)
        {
            if(len%i!=0)continue;
            bool flag=1;
            for(int j=i;j<len;j+=i)
            {
                if(has[i]!=has[j+i]-has[j]*power[i])
                {
                    flag=0;
                    // cout<<j<<"ooo"<<endl;
                    break;
                }
            }
            if(flag)
            {
                cout<<len/i<<endl;
                break;
            }
        }
    }
    return 0;
}

Seek the Name, Seek the Fame

还是枚举长度。

Code

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
using namespace std;
typedef unsigned long long ull;
#define b 131

ull power[400010];
ull has[400010];

int main()
{
    string s;
    
    power[0]=1;
    for(int i=1;i<=400000;i++)power[i]=power[i-1]*b;
    
    while(cin>>s)
    {
        int len=s.size();
        has[0]=0;
        for(int i=0;i<len;i++)has[i+1]=b*has[i]+(ull)(s[i]);
        
        for(int i=1;i<=len;i++)
        {
            if(has[i]==has[len]-has[len-i]*power[i])cout<<i<<" ";
        }
        cout<<endl;
    }
    return 0;
}

friends

由于 s s s 被复制过,所以 U U U 中一定含有一个完整的 s s s。枚举插入的位置。

Code

#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
#include<algorithm>
using namespace std;
typedef unsigned long long ull;
#define b 33

int n;
string s;
bool npr[500010];
int prime[42000],zxzys[500010],fac[30];
ull has[500010],power[500010];

void sprime()
{
    int cnt=0;
    for(int i=2;i<=n;i++)
    {
        if(!npr[i])prime[++cnt]=i,zxzys[i]=i;
        for(int j=1;prime[j]*i<=n&&j<=cnt;j++)
        {
            npr[prime[j]*i]=1;
            zxzys[prime[j]*i]=prime[j];
            if(i%prime[j]==0)break;
        }
    }
}

bool judge(int l,int r,int len)
{
    return has[r-len]-has[l-1]*power[r-l-len+1]==has[r]-has[l+len-1]*power[r-l-len+1];
}

int main()
{
    cin>>n>>s;
    
    sprime();
    power[0]=1;
    for(int i=1;i<=n;i++)power[i]=power[i-1]*b;
    has[0]=0;
    for(int i=1;i<=n;i++)has[i]=has[i-1]*b+(ull)(s[i-1]-'a'+1);
//    for(int i=1;i<=n;i++)cout<<has[i]<<" "<<power[i]<<endl;
    
    int q;
    
    cin>>q;
    
    for(;q;q--)
    {
        int l,r,cnt=0;
        scanf("%d%d",&l,&r);
        if(l==r)
        {
            puts("1");
            continue;
        }
        int len=r-l+1;
        while(len>1)
        {
            // cout<<zxzys[len]<<endl;
            // break;
            fac[++cnt]=zxzys[len];
            len/=fac[cnt];
        }
        len=r-l+1;
        for(int i=1;i<=cnt;i++)
        {
            if(judge(l,r,len/fac[i]))len/=fac[i];
        }
        printf("%d\n",len);
    }
    return 0;
}

P3498 [POI2010]KOR-Beads

枚举每个长度。判重可用 map。(set 也行)

Code

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<string>
#include<map>
using namespace std;
typedef unsigned long long ull;
#define b 1000000007

ull has1[200001],has2[200001],power[200001];
int s[200001],n;
int ansc=0,cnt=0,ans[200001],ansp=0;
map <ull,bool> mapp;

int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)cin>>s[i];
    
    power[0]=1;
    for(int i=1;i<=n;i++)power[i]=power[i-1]*b,has1[i]=has1[i-1]*b+s[i];
    for(int i=n;i>=1;i--)has2[i]=has2[i+1]*b+s[i];
    for(int i=1;i<=n/2;i++)
    {
        mapp.clear();
        cnt=0;
        for(int j=i;j<=n;j+=i)
        {
            if(n/i<ansp)break;//小小的特判
            ull h1=has1[j]-has1[j-i]*power[i],h2=has2[j-i+1]-has2[j+1]*power[i];
//            cout<<h1<<" "<<h2<<"ooo"<<endl;
            if(mapp[h1])continue;
            cnt++;
//            printf("(%d,%d)\n",j-i+1,j);
            mapp[h1]=1;
            mapp[h2]=1;
        }
        if(cnt>ansp)ansc=1,ans[ansc]=i,ansp=cnt;
        else if(cnt==ansp)ansc++,ans[ansc]=i;
//        cout<<i<<" "<<cnt<<endl;
    }
    if(ansp==1)
    {
        cout<<ansp<<" "<<ansc+n-n/2<<endl;
        for(int i=1;i<=ansc;i++)cout<<ans[i]<<" ";
        for(int i=n/2+1;i<=n;i++)cout<<i<<" ";
        return 0;
    }
    cout<<ansp<<" "<<ansc<<endl;
    for(int i=1;i<=ansc;i++)cout<<ans[i]<<" ";
}

[POI2010]ANT-Antisymmetry

暴力

单纯枚举子串中点,可以拿到 60 分的高分(LOJ 上竟有 82 分)。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
using namespace std;

string s;
bool str[500001];
int n;

int main()
{
    cin>>n>>s;
    for(int i=1;i<=n;i++)
    {
        str[i]=s[i-1]=='1'?1:0;
    }
    
    long long ans=0;
    for(int mid=1;mid<=n;mid++)
    {
        for(int i=0;mid>i&&mid+i+1<=n;i++)
        {
            if(!(str[mid-i]^str[mid+i+1]))break;
            ans++;
        }
    }
    cout<<ans<<endl;
    return 0;
}

正解

正解当然是 hash(废话)+二分。
原串求哈希,然后取反并倒序再求一遍,枚举子串中点然后二分子串长度。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
typedef unsigned long long ull;
using namespace std;

string s;
int str[500001];
ull has1[500001],has2[500001],power[500001];
int n;

bool check(int mid,int len)
{
    return has1[mid+len+1]-has1[mid-len-1]*power[len*2+2]==has2[mid-len]-has2[mid+len+2]*power[len*2+2];
}

int main()
{
    cin>>n>>s;
    for(int i=1;i<=n;i++)
    {
        str[i]=s[i-1]=='1'?1:0;
    }
    
    power[0]=1;
    for(int i=1;i<=n;i++)has1[i]=has1[i-1]*3+str[i],power[i]=power[i-1]*3;
    for(int i=n;i>=1;i--)has2[i]=has2[i+1]*3+1-str[i];
    
    long long ans=0;
    for(int mid=1;mid<=n;mid++)
    {
        int l=0,r=min(mid-1,n-mid-1),len=0;
        if(r==l)
        {
            if(str[mid]^str[mid+1])ans++;
            continue;
        }
        while(l<=r)
        {
            len=(l+r)/2;
            if(check(mid,len))l=len+1;
            else r=len-1;
        }
        ans+=l;
    }
    cout<<ans<<endl;
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值