Loading

字符串杂题

P1709 [USACO5.5] 隐藏口令 Hidden Password
实际就是求最小表示法。首先一个直观的思路是我们将最小的字符存下来然后进行比较,但是如果是"aaa···aaa"仍然会被卡成\(O(N^2)\).考虑优化,发现如果我们记下每个最小字符之后第一个大于这个字符的字符,那么我们就可以判掉这种情况,但是如果遇见"abab···ababab"仍然会炸.考虑我们的瓶颈(也就是为什么复杂度会这么高即做了哪些重复工作),是我们对于以每个位子开头的字符串都要比较一次,有一个关于字符串算法的核心思想:

基于所求信息的特殊性质与已经求出的信息,使用增量法与势能分析求得所有信息。这体现了动态规划思想。

显然这对于解字符串类型的题目也有参考价值.我们考虑如果我们比较了以i,j两个位置开头的字符串,那么可以省略什么,发现如果第p次出现不同.即s[i+p]≠s[j+p],不妨令s[i+p]>s[j+p],那么我们发现i ~ i+p之间的位置一定不是答案,因为s[i ~ i+p],与s[j ~ j+p]一样,所以如果有一个答案以s[i+k]开头(0<=k<=P),那么s[j+k]开头的一定更小.
所以我们用两个指针,p,q开始指向0,1.枚举k每次比较,如果p+k<q+k,那么q变为q+k+1,至于循环问题,破环成链一下就好.时间复杂度O(N)
注意如果发现p==q,要手动把q+1,还有要保证l<r
详细算法内容

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define N_
#define P pair<ll,ll>
string s;
ll N;
int main()
{   
    // freopen("P1709.out","w",stdout);
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    cin>>N;
    char ch;
    while(cin>>ch)
    {   
        if(isspace(ch))continue;
        s+=ch;
    }
    s+=s;
    ll l=0,r=1;
    while(1)
    {
        bool f=1;
        for(int i=0;i<N;i++)
        {
            if(s[l+i]<s[r+i]){
                r=r+i+1;
                f=0;
                break;
            }
            if(s[l+i]>s[r+i]){
                l=l+i+1;
                if(l==r)r++;
                f=0;
                break;
            }
        }
        if(f)r++;
        if(l>r)swap(l,r);
        // cout<<l<<" "<<r<<endl;
        if(r>=N)break;
    }
    cout<<l;
    return 0;
}

小结:关于字符串核心思想要掌握,暴力优化考虑重复做了哪些事
P2102 地砖铺设
一个贪心题,然后暴力模拟即可.
小结:能暴力就暴力,先保证正确性
P3426 [POI 2005] SZA-Template
这种匹配问题首先考虑的肯定是kmp,但是我们发现这个是可以重复的匹配。如果是暴力,那么要NNN,根据字符串的核心思想,我们想到dp。那么因为是kmp我们大胆猜测,答案可能是broader或本身,现在问题变成如何用前缀信息解决i。如果答案是bd,那么i的bd的bd一定是i的bd,所以答案可能包含dp[j](其中j是i的bd),那么根据一系列证明我们发现猜测是正确的(具体证明比较繁琐,笔者不是很懂)
那么,dp[\(bd_{max}\)]如何转移到dp[i]呢,我们记h[u]表示前缀u可以贡献到的最远距离,那么如果h[dp[\(bd_{max}\)]]>\(i-bd_max\),也就是大于其最大的循环,那么就可以贡献,如下图
无标题

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define P pair<ll,ll>
#define N_ 1000055
ll f[N_],h[N_],p[N_];
char S[N_];
int main()
{
    cin>>S+1;
    ll N=strlen(S+1);
    for(int j=0,i=2;i<=N;i++){
        while(j&&S[j+1]!=S[i])j=p[j];
        p[i]=j+=(S[i]==S[j+1]);
    }
    f[0]=1;
    for(int i=1;i<=N;i++)
    { 
        f[i]=i;
        if(h[f[p[i]]]>=i-p[i])f[i]=f[p[i]];
        h[f[i]]=i;
    }
    cout<<f[N];
    return 0;
}

关于用kmp匹配字符串
要先将模式串做一次kmp求出p数组,然后文本串再根据p数组匹配模式串
同理
考虑用kmp求一次模式串,然后文本串匹配一次模式串,匹配过程中如果匹配次数大于模式串长度,就连续出栈,否则就入栈

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define P pair<ll,ll> 
#define N_ 1000005
string S,T;
ll p[N_],H[N_],top,q[N_]; 
int main()
{
    cin>>S>>T;
    for(int i=1,j=0;i<T.size();i++)
    {
        while(j&&T[i]!=T[j])j=p[j-1];
        p[i]=j+=(T[i]==T[j]);
    }
    for(int i=0,j=0;i<S.size();i++)
    {
        while(j&&T[j]!=S[i])j=p[j-1];
        H[i]=j+=(T[j]==S[i]);
        if(j>=T.size())
        {
            top-=j;
            top++;
            j=H[q[top]];
            // cout<<q[top]<<endl;
        }
        else
        {
            ++top,q[top]=i;
        }
    }
    // for(int i=0;i<S.size();i++)
    // cout<<H[i]<<" ";
    // cout<<endl;
    // for(int i=1;i<=top;i++)
    // cout<<q[top]<<" ";
    // cout<<endl;
    for(int i=1;i<=top;i++)
    cout<<S[q[i]];
    return 0;
}

小结:发现这种匹配就删除的东西,一般要用栈
KMP好题
首先一个暴力思路,是直接暴力跳p数组,直到长度小于前缀长度一半,但是如果题目数据是1e6个a,就会被卡成N*N,考虑优化
1.发现这种东西非常可以用倍增优化,具体的将每个位置跳\(2^k\)的j求出来,那么查询的时候,就可以log时间查
2.发现每次条p数组很浪费,考虑到字符串核心思想,我们可以想到每次最终j都会i/2,那我们不妨保留j,这样下一次就可以不用重复跳

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define N_ 1000005
ll T,p[N_],cnt[N_];
string S;
int main()
{
    cin>>T;
    while(T--)
    {
        cin>>S;
        cnt[1]=1;
        for(int j=0,i=1;i<S.size();i++)
        {
            while(j&&S[i]!=S[j])j=p[j-1];
            p[i]=j+=(S[i]==S[j]);
            cnt[i+1]=cnt[j]+1;
        }
        ll ans=1;
        for(int i=0,j=0;i<S.size();i++)
        {
            while(j&&S[i]!=S[j])j=p[j-1];
            j+=(S[i]==S[j]);
            while(j>(i+1)/2)j=p[j-1];
            ans=ans*(cnt[j]+1)%1000000007;
        }
        cout<<ans<<'\n';
        memset(p,0,sizeof(p));
        memset(cnt,0,sizeof(cnt));
    }
    return 0;
}

小结:字符串核心思想

posted @ 2025-11-26 20:32  leaf_ydc  阅读(8)  评论(0)    收藏  举报