KMP题集

HDU - 1711

模板题。。

#include
using namespace std;
const int N=1e6+7;
int a[N],b[N],n,m,nxt[N];
void getNext(int s[],int n,int nxt[])
{
    int i=0,j=nxt[0]=-1;
    while(iwhile(-1!=j&&s[i]!=s[j]) j=nxt[j];
        if(s[++i]==s[++j]) nxt[i]=nxt[j];
        else nxt[i]=j;
    }
}
int kmp(int s[],int n,int pt[],int m)
{
    int i=0,j=0;
    while(iwhile(-1!=j&&s[i]!=pt[j]) j=nxt[j];
        ++i;++j;
        if(j>=m) return i-m+1;
    }
    return -1;
}
int main ()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d",&n,&m);
        for(int i=0;iscanf("%d",&a[i]);
        for(int j=0;jscanf("%d",&b[j]);
        getNext(b,m,nxt);
        printf("%d\n",kmp(a,n,b,m));
    }
    return 0;
}

HDU - 3746

用kmp处理出next数组。最后要形成的字符串一定是原字符串的后缀是原字符串的一个前缀,最后前缀-后缀加到后面。因此要找到最小的字符串。

lennext[len] 来求出最小循环节。

如果是abcdefgab这种,循环节是 abcdefg 。

如果是ababababa这种,循环节就是ab。

最后答案就是往后添加多少字符能变成循环节的倍数。

#include
using namespace std;
const int N=1e5+7;
char s[N];
int nxt[N];
void getNext(char s[],int n,int nxt[])
{
    int i=0,j=nxt[0]=-1;
    while(iwhile(-1!=j&&s[i]!=s[j]) j=nxt[j];
        ++i;++j;
        nxt[i]=j;
    }
}
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%s",s);
        int n=strlen(s);
        getNext(s,n,nxt);
        int circle = n-nxt[n],ans;
        if(n%circle==0)
        {
            if(n==circle) ans=n;
            else ans=0;
        }
        else ans=(-n)%circle+circle;
        printf("%d\n",ans);
    }
    return 0;
}

HDU - 1358

跟上一题差不多。。。。

#include
using namespace std;
const int N=1e6+7;
char s[N];
int nxt[N];
void getNext(char s[],int n,int nxt[])
{
    int i=0,j=nxt[0]=-1;
    while(iwhile(-1!=j&&s[i]!=s[j]) j=nxt[j];
        ++i;++j;
        nxt[i]=j;
    }
}
int main ()
{
    int n,kase=1;
    while(~scanf("%d",&n))
    {
        if(n==0) break;
        scanf("%s",s);
        getNext(s,n,nxt);
        printf("Test case #%d\n",kase++);
        for(int i=2;i<=n;++i)
        {
            int c=i-nxt[i];
            if(i%c==0&&i!=c)
                printf("%d %d\n",i,i/c);
        }
        puts("");
    }
    return 0;
}

HDU - 2594

求最长的相等的 s1 前缀和 s2 后缀。

s1 求next,然后 s2 为主串 s1 为模式串做kmp就行了。

#include
using namespace std;
const int N=5e4+7;
char a[N],b[N];
int nxt[N];
void getNext(char s[],int n,int nxt[])
{
    int i=0,j=nxt[0]=-1;
    while(iwhile(-1!=j&&s[i]!=s[j]) j=nxt[j];
        if(s[++i]==s[++j]) nxt[i]=nxt[j];
        else nxt[i]=j;
    }
}
int kmp(char s[],int n,char pt[],int m)
{
    int i=0,j=0;
    while(iwhile(-1!=j&&s[i]!=pt[j]) j=nxt[j];
        ++i;++j;
    }
    return j;
}
int main()
{
    while(~scanf("%s %s",a,b))
    {
        int n=strlen(a);
        int m=strlen(b);
        getNext(a,n,nxt);
        int ans=kmp(b,m,a,n);
        if(ans==0) puts("0");
        else printf("%s %d\n",b+m-ans,ans);
    }
    return 0;
}

HDU - 3336

跟AC自动机dp差不多。。。

dp[i] 表示 s[1...i] 这个串的后缀等于 s 串前缀的数量。

dp[i]=dp[next[i]]+1

#include
using namespace std;
const int N=2e5+7;
const int mod=1e4+7;
char s[N];
int nxt[N],dp[N];
void getNext(char s[],int n,int nxt[])
{
    int i=0,j=nxt[0]=-1;
    while(iwhile(-1!=j&&s[i]!=s[j]) j=nxt[j];
        ++i;++j;
        nxt[i]=j;
    }
}
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        int n;
        scanf("%d",&n);
        scanf("%s",s);
        getNext(s,n,nxt);
        int ans=0;
        for(int i=1;i<=n;++i)
        {
            dp[i]=dp[nxt[i]]+1;
            ans=(ans+dp[i])%mod;
        }
        printf("%d\n",ans);
    }
    return 0;
}

HDU - 3374

题意是求出做任意次将最前面的字母放到最后面的操作后,字典序最小的串。

算法就是令 i=0,j=1 。表示以 i 开头的串和以 j 开头的串。每次找出第一个 k 使得 s[i+k]!=s[j+k] 。如果 s[i+k]>s[j+k] ,那么 i 串不可能是最小表示,令 i=i+k+1 ,继续。同理,若 s[i+k]>s[j+k] ,令 j=j+k+1 ,继续。若 i=j ,要使得 j+=1 。最后 ij 较小的那个就是答案。

这样相当于 O(n) 地比较了 n 个不同的串。

#include
using namespace std;
const int N=1e6+7;
char s[N];
int nxt[N],n;
void getNext()
{
    int i=0,j=nxt[0]=-1;
    while(iwhile(-1!=j&&s[i]!=s[j]) j=nxt[j];
        if(s[++i]==s[++j]) nxt[i]=nxt[j];
        else nxt[i]=j;
    }
}
int getMin()
{
    int i=0,j=1,l;
    while(ifor(l=0;lif(s[(i+l)%n]!=s[(j+l)%n]) break;
        if(l==n) break;
        if(s[(i+l)%n]>s[(j+l)%n]) i=i+l+1;
        else j=j+l+1;
        if(i==j) ++j;
    }
    return min(i,j);
}
int getMax()
{
    int i=0,j=1,l;
    while(ifor(l=0;lif(s[(i+l)%n]!=s[(j+l)%n]) break;
        if(l==n) break;
        if(s[(i+l)%n]<s[(j+l)%n]) i=i+l+1;
        else j=j+l+1;
        if(i==j) ++j;
    }
    return min(i,j);
}
int main ()
{
    while(~scanf("%s",s))
    {
        n=strlen(s);
        int cir;
        getNext();
        if(n%(n-nxt[n])==0) cir=n/(n-nxt[n]);
        else cir=1;
        printf("%d %d %d %d\n",getMin()+1,cir,getMax()+1,cir);
    }
    return 0;
}

HDU - 3613

题意是求将一个串分成两段,求价值之和的最大值。

一个串的价值定义为:如果它是回文串,价值为所有位权值之和,否则价值为 0

做Manacher算法,求出前缀是否回文串和后缀是否回文串两个数组,扫一遍即可。

#include
using namespace std;
const int N = 5e5+7;
char tmp[N*2];
int val[26],ma[N*2];
bool a[N*2],b[N*2];
void manache(string &s,int len,char tmp[],int a[])
{
    int l=0;
    tmp[l++]='$';
    tmp[l++]='#';
    for(int i=0;is[i];
        tmp[l++]='#';
    }
    tmp[l]=0;
    int mx=0,id=0;
    for(int i=0;ii?min(a[2*id-i],mx-i):1;
        while(tmp[a[i]+i]==tmp[i-a[i]]) ++a[i];
        if(i+a[i]>mx)
        {
            mx=i+a[i];
            id=i;
        }
    }
}
int main ()
{
    ios::sync_with_stdio(false);
    int T;
    cin >> T;
    while(T--)
    {
        for(int i=0;i<26;++i) cin >> val[i];
        string s;
        cin >> s;
        int n=s.length();
        memset(a,0,sizeof(a));
        memset(b,0,sizeof(b));
        manache(s,n,tmp,ma);
        for(int i=1;i<2*n+2;++i)
            if(i-ma[i]==0) a[ma[i]+i-1]=1;
        for(int i=2*n;i>0;--i)
            if(i+ma[i]==2*n+2) b[i-ma[i]+1]=1;
        int ans=0,pre=0,last=0;
        for(int i=0;ilast+=val[s[i]-'a'];
        for(int i=2;i*2;++i)
        {
            if(i%2==0)
            {
                int v = val[s[(i>>1)-1]-'a'];
                pre += v;
                last -= v;
            }
            int res=0;
            if(a[i]) res+=pre;
            if(b[i]) res+=last;
            ans=max(ans,res);
        }
        cout << ans << '\n';
    }
    return 0;
}

POJ - 3376

两个串相连是回文串,当且仅当长度小的那个串翻转后是长度大的那个串的前缀,且减去前缀后剩下的部分也是回文串。因此对与一个串,要查询是多少串的前缀。

将字符串从大到小排序,插入Trie中,每次插入后,对该串进行一次查询。Trie上的每个点应该有权值,表明减去前缀后的串是否是回文串。这个可以通过Manacher算法先预处理出来。

这里我写的复杂了点。。其实不需要对长度排序,也不需要插两次。只需要记录每个串的前缀是否是回文串和后缀是否是回文串就行了。

#include
#include
#include
using namespace std;
typedef long long ll;
const int N = 2e6+7;
struct Trie
{
    int nxt[N][26],end[N],L,root;
    int newnode()
    {
        for(int i=0;i<26;++i)
            nxt[L][i]=-1;
        end[L]=0;
        return L++;
    }
    void init()
    {
        L=0;
        root=newnode();
    }
    void insert1(char buf[],bool ok[],int n)
    {
        int now=root;
        for(int i=0;iif(nxt[now][buf[i]-'a']==-1)
                nxt[now][buf[i]-'a']=newnode();
            now = nxt[now][buf[i]-'a'];
            if(ok[i]) ++end[now];
        }
    }
    void insert2(char buf[],bool ok[],int n)
    {
        int now=root;
        for(int i=n-1;i>=0;--i)
        {
            if(nxt[now][buf[i]-'a']==-1)
                nxt[now][buf[i]-'a']=newnode();
            now = nxt[now][buf[i]-'a'];
            if(ok[i]) ++end[now];
        }
    }
    int query1(char buf[],int n)
    {
        int now=root;
        for(int i=n-1;i>=0;--i)
        {
            if(nxt[now][buf[i]-'a']==-1) return 0;
            now=nxt[now][buf[i]-'a'];
        }
        return end[now];
    }
    int query2(char buf[],int n)
    {
        int now=root;
        for(int i=0;iif(nxt[now][buf[i]-'a']==-1) return 0;
            now=nxt[now][buf[i]-'a'];
        }
        return end[now];
    }
}t;
char s[N],tmp[N*2];
int ma[N*2];
bool ok[N];
bool manache(char s[],int len,char tmp[],int a[],bool ok[])
{
    int l=0;
    tmp[l++]='$';
    tmp[l++]='#';
    for(int i=0;i'#';
    }
    tmp[l]=0;
    int mx=0,id=0;
    for(int i=0;ii?min(a[2*id-i],mx-i):1;
        while(tmp[a[i]+i]==tmp[i-a[i]]) ++a[i];
        if(i+a[i]>mx)
        {
            mx=i+a[i];
            id=i;
        }
    }
}
bool deal1(int ma[],int len,bool ok[])
{

    for(int i=0;i0;
    bool nice=false;
    for(int i=2;i2+2;++i)
    {
        if(ma[i]+i==len*2+2)
        {
            int id=i-ma[i]+2;
            id=(id>>1)-2;
            if(id>=0) ok[id]=true;
            if(id==-1) nice=true;
        }
    }
    ok[len-1]=true;
    return nice;
}
bool deal2(int ma[],int len,bool ok[])
{
    for(int i=0;i0;
    bool nice=false;
    for(int i=2;i2+2;++i)
    {
        if(i-ma[i]==0)
        {
            int id=i+ma[i]-2;
            id=(id>>1);
            if(idid]=true;
            if(id==len) nice=true;
        }
    }
    ok[0]=true;
    return nice;
}
int len[N],pos[N],p[N];
bool cmp(int x,int y)
{
    return len[x]>len[y];
}
int main()
{
    int n;
    while(~scanf("%d",&n))
    {
        int tot=0;
        for(int i=0;i"%d%s",&len[i],s+tot);
            pos[i]=tot;
            tot+=len[i];
            p[i]=i;
        }
        sort(p,p+n,cmp);
        t.init();
        ll ans=0;
        for(int i=0;iint id=p[i];
            manache(s+pos[id],len[id],tmp,ma,ok);
            if(deal1(ma,len[id],ok)) --ans;
            t.insert1(s+pos[id],ok,len[id]);
            ans+=t.query1(s+pos[id],len[id]);
        }
        t.init();
        for(int i=0;iint id=p[i];
            manache(s+pos[id],len[id],tmp,ma,ok);
            deal2(ma,len[id],ok);
            t.insert2(s+pos[id],ok,len[id]);
            ans+=t.query2(s+pos[id],len[id]);
        }
        printf("%I64d\n",ans);
    }
    return 0;
}

你可能感兴趣的:(KMP)