KMP合集

本文介绍了多种字符串处理和模式匹配算法的应用,包括KMP算法、扩展KMP算法以及它们在查找最长公共子串、字符串匹配、循环串处理等问题上的应用。讨论了如何利用这些算法解决实际问题,如找出字符串的最小表示法、最长公共前缀等。还涵盖了其他相关问题,如字符串的回文判断和循环节计算。
摘要由CSDN通过智能技术生成

题意:给你两个串,问你第二个串是从第一个串的什么位置开始完全匹配的? kmp裸题,复杂度O(n+m)。

当一个字符串以0为起始下标时,next[i]可以描述为"不为自身的最大首尾重复子串长度"。

当发生失配的情况下,j的新值next[j]取决于模式串中T[0 ~ j-1]中前缀和后缀相等部分的长度, 并且next[j]恰好等于这个最大长度。

防止超时,注意一些细节。。

另外:尽量少用strlen,变量记录下来使用比较好,用字符数组而不用string

#include <bits/stdc++.h>
//#pragma GCC optimize(2)
//#pragma comment(linker, "/STACK:102400000,102400000")
using namespace std;
typedef long long ll;
const int maxn=1e6+5;
const int maxm=1e4+5;
int a[maxn];
int b[maxn];
int Next[maxn];
int n,m;
void init() {

}
void getNext()
{
    int k=-1;
    int j=0;
    Next[j]=k;
    while(j<m)
    {
        if(k==-1||b[k]==b[j])
        {
            k++;
            j++;
            Next[j]=k;
        }
        else
            k=Next[k];
    }
}
int KMP(int start)
{
    int i=start,j=0;
    while(i<n&&j<m)
    {
        if(j==-1||a[i]==b[j])
        {
            i++,j++;
        }
        else
            j=Next[j];
    }
    if(j==m)
        return i-j+1;
    return -1;
}
void solve() {

    cin>>n>>m;
    for(int i=0;i<n;i++)
    {
        cin>>a[i];
    }
    for(int i=0;i<m;i++)
    {
        cin>>b[i];
    }
    getNext();
    cout<<KMP(0)<<"\n";
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    int _ = 1;
    cin>>_;
    while (_--) {
        init();
        solve();
    }
    return 0;
}

题目翻译:给出一个模式串,给出一个文本串,求模式串在文本串中出现

了多少次?

#include <bits/stdc++.h>
//#pragma GCC optimize(2)
//#pragma comment(linker, "/STACK:102400000,102400000")
using namespace std;
typedef long long ll;
const int maxn=1e6+5;
const int maxm=1e4+5;
string a,b;
int n,m;
int Next[maxn];
void init() {

}
void getNext()
{
    int k=-1;
    int j=0;
    Next[j]=k;
    while(j<m)
    {
        if(k==-1||b[k]==b[j])
        {
            k++;
            j++;
            Next[j]=k;
        }
        else
            k=Next[k];
    }
}
int KMP(int start)
{
    int ans=0;
    int i=start,j=0;
    while(i<n)
    {
        if(j==-1||a[i]==b[j])
        {
            i++,j++;
        }
        else
            j=Next[j];
        if(j==m)
        {
            ans++;
        }
    }
    return ans;
}
void solve() {
    cin>>b>>a;
    n=a.size();
    m=b.size();
    getNext();
    cout<<KMP(0)<<"\n";
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    int _ = 1;
    cin>>_;
    while (_--) {
        init();
        solve();
    }
    return 0;
}

题意:输入两个字符串,问第二个字符串在第一个字符串中的出现次数。(不可可重叠)。

#include <bits/stdc++.h>
//#pragma GCC optimize(2)
//#pragma comment(linker, "/STACK:102400000,102400000")
using namespace std;
typedef long long ll;
const int maxn=1e6+5;
const int maxm=1e4+5;
string a,b;
int n,m;
int Next[maxn];
void init() {

}
void getNext()
{
    int k=-1;
    int j=0;
    Next[j]=k;
    while(j<m)
    {
        if(k==-1||b[k]==b[j])
        {
            k++;
            j++;
            Next[j]=k;
        }
        else
            k=Next[k];
    }
}
int KMP(int start)
{
    int ans=0;
    int i=start,j=0;
    while(i<n)
    {
        if(j==-1||a[i]==b[j])
        {
            i++,j++;
        }
        else
            j=Next[j];
        if(j==m)
        {
            ans++;
            j=0;
        }
    }
    return ans;
}
void solve() {
    while(cin>>a)
    {
        if(a[0]=='#')
            return;
        cin>>b;
        n=a.size();
        m=b.size();
        getNext();
        cout<<KMP(0)<<"\n";
    }

}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    int _ = 1;
    //cin>>_;
    while (_--) {
        init();
        solve();
    }
    return 0;
}

题目大意:
给一个字符串,问再加几个字符能构成循环

题目分析:
我们可以利用next数组的性质 求得串的最小循环元大小,最小循环元大小等于len - next[末尾位置],如果串的长度能够整除循环元,则原串已经循环,不需要补全,否则,循环元大小减去串的长度对最小循环元的余数即为需要补的字符数。

#include <bits/stdc++.h>
//#pragma GCC optimize(2)
//#pragma comment(linker, "/STACK:102400000,102400000")
using namespace std;
typedef long long ll;
const int maxn=1e6+5;
int Next[maxn];
void init() {

}
int n;
string s;
void getNext()
{
    int k=-1;
    int j=0;
    Next[j]=k;
    while(j<n)
    {
        if(k==-1||s[k]==s[j])
        {
            k++;
            j++;
            Next[j]=k;
        }
        else
            k=Next[k];
    }
}
void solve() {
    cin>>s;
    n=s.size();
    getNext();
    int l=n-Next[n];
    if(n%l==0&&n!=l)
        cout<<0<<endl;
    else
        cout<<l-n%l<<endl;
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    int _ = 1;
    cin>>_;
    while (_--) {
        init();
        solve();
    }
    return 0;
}

题意
给一字符串,求其所有完整循环的前缀与循环节的长度。
例:aaa
长度2前缀,循环节为a,个数为2
长度3前缀,循环节为a,个数为3

思路
kmp求出字符串前后缀重复数,遍历所有前缀子串进行下面操作:
字符串前后缀重复数next[L],则循环节的长度为L-L%next[L],如果L%循环节长度为0,则说明是完整循环,输出解。

#include <bits/stdc++.h>
//#pragma GCC optimize(2)
//#pragma comment(linker, "/STACK:102400000,102400000")
using namespace std;
typedef long long ll;
const int maxn=1e6+5;
int Next[maxn];
void init() {

}
int n;
string s;
void getNext()
{
    int k=-1;
    int j=0;
    Next[j]=k;
    while(j<n)
    {
        if(k==-1||s[k]==s[j])
        {
            k++;
            j++;
            Next[j]=k;
        }
        else
            k=Next[k];
    }
}
void solve() {
    int t=0;
    while(cin>>n&&n)
    {
        t++;
        cin>>s;
        getNext();
        cout<<"Test case #"<<t<<endl;
        for(int i=2;i<=n;i++)
        {
            int l=i-Next[i];
            if(i%l==0&&i/l!=1)
            {
                cout<<i<<" "<<i/l<<endl;
            }
        }
        cout<<endl;
    }
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    int _ = 1;
    //cin>>_;
    while (_--) {
        init();
        solve();
    }
    return 0;
}

【题意】

给出一个字符串,要把它写成(x)n的形式,问n的最大值。

【思路】

方法一:后缀数组 (2485MS)

用后缀数组求出字符串的各种信息:Rank数组,height数组等等。

然后我们从1开始枚举循环节的长度i。如果字符串存在长度大于等于2的循环节,需要满足以下条件

首先循环节的长度必须是字符串长度的因子。

要保证Rank[0]==Rank[i]+1,因为两者比较字典序的时候前面的都相同,只是原串更长一些。

而且从i开始的后缀与原串的公共前缀为前者的长度即len-i。

否则结果就为1。

PS: 此题数据范围较大,用DA算法会超时,用DC3算法也只是刚刚过,所以建议用下面的方法。

方法二: KMP (141MS)

设字符串的长度为len,我们知道nex[len]表示既是原串前缀又是原串后缀的字符串最大长度(不包括本身),所以如果字符串的循环节长度大于等于2,那么len-nex[len]一定是字符串的最小周期,且一定能整除len。否则输出1。

//#include <bits/stdc++.h>
#include<iostream>

//#pragma GCC optimize(2)
//#pragma comment(linker, "/STACK:102400000,102400000")
using namespace std;
typedef long long ll;
const int maxn=1e6+5;
int Next[maxn];
void init() {

}
int n;
string s;
void getNext()
{
    int k=-1;
    int j=0;
    Next[j]=k;
    while(j<n)
    {
        if(k==-1||s[k]==s[j])
        {
            k++;
            j++;
            Next[j]=k;
        }
        else
            k=Next[k];
    }
}
void solve() {
    while(cin>>s)
    {
        if(s[0]=='.')
            return;
        n=s.size();
        getNext();
        int l=n-Next[n];
        if(n%l==0)
            cout<<n/l<<endl;
        else
            cout<<1<<endl;
    }
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    int _ = 1;
    //cin>>_;
    while (_--) {
        init();
        solve();
    }
    return 0;
}

给定一个字符串,求出所有既是前缀又是后缀的子串。

思路
KMP 的next数组的运用(改进前),如果next数组中next[ i ]= k 正值,那么就代表这从 “0 ~ i-1”位置的子串的前缀后缀相同的最大长度为k,并且k代表着与当前后缀最大匹配的前缀位置。

0	1	2	3	4	5	6	7	8	9	10	11	12

字符串 a a b a a b a a b a a b
next [ ] -1 0 1 0 1 2 3 4 5 6 7 8 9

如表 next[ 12 ] = 9 , 9 所在位置的前缀 为 a a b a a b a a b,与后缀是最大匹配 , next[ 9 ] = 6 ,6 所在位置的前缀 为 a a b a a b,仍然和后缀匹配,第二大匹配
next[ 6 ] = 3 , ,a a b, next[ 3 ] = 0,表明需要回溯到头再进行匹配。
因而通过前后缀重复次数的计数,以及next数组的调转,我们就能尽可能少的重复 重复位置的匹配。每次都能跳转到 前后缀最大公共的位置。

因而对于这个题,我们从最后的next[ len ]开始,不断地找 next[next[i]], 所找的前缀都和最后的后缀所匹配,所在位置的值就是前后缀相同的长度。全部累加就可以了。
另外还要加上满足条件的字符串本身。

//#include <bits/stdc++.h>
#include<iostream>
#include<vector>
//#pragma GCC optimize(2)
//#pragma comment(linker, "/STACK:102400000,102400000")
using namespace std;
typedef long long ll;
const int maxn=1e6+5;
int Next[maxn];
vector<int>vv;
void init() {
    vv.clear();
}
int n;
string s;
void getNext()
{
    int k=-1;
    int j=0;
    Next[j]=k;
    while(j<n)
    {
        if(k==-1||s[k]==s[j])
        {
            k++;
            j++;
            Next[j]=k;
        }
        else
            k=Next[k];
    }
}
void solve() {
    while(cin>>s)
    {
        init();
        n=s.size();
        getNext();
        vv.push_back(n);
        int x=Next[n];
        while(x)
        {
            vv.push_back(x);
            x=Next[x];
        }
        for(int i=vv.size()-1;i>=0;i--)
            cout<<vv[i]<<" ";
        cout<<endl;
    }
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    int _ = 1;
    //cin>>_;
    while (_--) {
        init();
        solve();
    }
    return 0;
}

题目大意:给你N个长度为60的字符串(N<=10),求他们的最长公共子串(长度>=3)。
题目分析:KMP字符串匹配基础题。直接枚举第1个字符串的所有子串,判断这个子串是否出现在另外N-1个串中

//#include <bits/stdc++.h>
#include<iostream>
#include<vector>
#include<algorithm>
//#pragma GCC optimize(2)
//#pragma comment(linker, "/STACK:102400000,102400000")
using namespace std;
typedef long long ll;
const int maxn=1e6+5;
int Next[maxn];
int m;
string t,p;
vector<string >vv;
vector<string >uu;
void init() {
    uu.clear();
    vv.clear();
}
bool cmp(string a,string b)
{
    if(a.size()==b.size())
    {
        return a<b;
    }
    return a.size()>b.size();
}
void getNext()
{
    int k=-1;
    int j=0;
    Next[j]=k;
    while(j<m)
    {
        if(k==-1||t[k]==t[j])
        {
            k++;
            j++;
            Next[j]=k;
        }
        else
            k=Next[k];
    }
}
bool KMP(int start)
{
    int i=start,j=0;
    while(i<60&&j<m)
    {
        if(j==-1||p[i]==t[j])
        {
            i++,j++;
        }
        else
            j=Next[j];
    }
    if(j==m)
        return true;
    return false;
}
void solve() {
    string s;
    int n;
    cin>>n;
    for(int i=0;i<n;i++)
    {
        cin>>s;
        uu.push_back(s);
    }
    for(int i=0;i<60;i++)
    {
        for(int j=3;i+j<=60;j++)
        {
            vv.push_back(uu[0].substr(i,j));
        }
    }
    sort(vv.begin(),vv.end(),cmp);
    vv.erase(unique(vv.begin(),vv.end()),vv.end());
    int flag;
    for(int i=0;i<vv.size();i++)
    {
        t=vv[i];
        m=t.size();
        getNext();
        flag=1;
        for(int j=0;j<uu.size();j++)
        {
            p=uu[j];
            if(KMP(0))
            {

            }
            else
            {
                flag=0;
                break;
            }
        }
        if(flag==1)
        {
            cout<<t<<endl;
            return;
        }
    }
    cout<<"no significant commonalities"<<endl;

}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    int _ = 1;
    cin>>_;
    while (_--) {
        init();
        solve();
    }
    return 0;
}

题目大意:给定两个字符串,在第一个字符串中找到一个最大前缀作为第二个字符串的后缀

思路:将S1作为模式串 然后在s2中寻找,求出s1的next数组,nxt[i]:x[i…m-1]与x[0…m-1]的最长公共前缀。同时求出s2的extend数组,extend[i]:y[i…n-1]与x[0…m-1]的最长公共前缀

//#include <bits/stdc++.h>
#include<iostream>
#include<vector>
#include<algorithm>
//#pragma GCC optimize(2)
//#pragma comment(linker, "/STACK:102400000,102400000")
using namespace std;
typedef long long ll;
const int maxn=1e6+5;
int Next[maxn];
int m;
string t,p;
void init() {

}
void getNext()
{
    int k=-1;
    int j=0;
    Next[j]=k;
    while(j<m)
    {
        if(k==-1||t[k]==t[j])
        {
            k++;
            j++;
            Next[j]=k;
        }
        else
            k=Next[k];
    }
}
bool KMP(int start)
{
    int i=start,j=0;
    while(i<60&&j<m)
    {
        if(j==-1||p[i]==t[j])
        {
            i++,j++;
        }
        else
            j=Next[j];
    }
    if(j==m)
        return true;
    return false;
}
void solve() {
    string a,b;
    while(cin>>a>>b)
    {
        int min_=min(a.size(),b.size());
        t=a+b;
        m=t.size();
        getNext();
        int xx=Next[m];
        while(xx>min_)
        {
            xx=Next[xx];
        }
        if(xx==0)
        {
            cout<<0<<endl;
        }
        else
        {
            cout<<a.substr(0,xx);
            cout<<" "<<xx<<endl;
        }
    }

}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    int _ = 1;
    //cin>>_;
    while (_--) {
        init();
        solve();
    }
    return 0;
}

题目大意:

发送一个密文,为字符串S。这段密文的前半部份是加密过的,后半部分是没有加密过的。现在这段密文被截获,但是密文的尾部的一部份损失了。例如,假设密文是xxxxzzzz, xxxx是加密过的,zzzz是没加密的,因为损失了后面一部份,所以截获的内容可能为xxxxzz, 可以保证截获的内容的无加密内容一定是完整的。

给出密码表,这个密码表为一串26个字母的字符串,a[0]表示a加密后的字母,a[1]表示b加密后的字母…a[25]表示z加密后的字母。

要你恢复到无损坏的密文。在截获的内容中,要让明文最短。

分析与总结:

先来看看暴力的方法是怎样的。直接从S的一半位置开始枚举,然后判断后面部分的经过加密后是否都和明文部分相匹配即可。

事实证明,数据弱了,可以暴力水过。

如果不水,这题的正解应该是用拓展KMP(资料)

先把S全部都当作是密文的,然后把S全转换成明文,保存为T

这时,S中的密文部分就全部都变成了明文,而明文部分都变成了xxx(不用管是什么)。

然后可以发现,原来的S中的明文部分是S的后缀,而T中的明文部分是T的前缀。

所以,演变成了求S【i…n】中的与T的最长公共前缀,就是赤裸的拓展KMP问题了。

//#include <bits/stdc++.h>
#include<iostream>
#include<vector>
#include<algorithm>
//#pragma GCC optimize(2)
//#pragma comment(linker, "/STACK:102400000,102400000")
using namespace std;
typedef long long ll;
const int maxn=1e6+5;
int Next[maxn];
int m;
string t,p;
void init() {

}
void getNext()
{
    int k=-1;
    int j=0;
    Next[j]=k;
    while(j<m)
    {
        if(k==-1||t[k]==t[j])
        {
            k++;
            j++;
            Next[j]=k;
        }
        else
            k=Next[k];
    }
}
bool KMP(int start)
{
    int i=start,j=0;
    while(i<60&&j<m)
    {
        if(j==-1||p[i]==t[j])
        {
            i++,j++;
        }
        else
            j=Next[j];
    }
    if(j==m)
        return true;
    return false;
}
void solve() {
    cin>>p>>t;
    string s=t,ss=p;
    m=t.size();
    for(int i=m/2;i<m;i++)
    {
        t[i]=p[t[i]-'a'];
    }
    getNext();
    int xx=m;
    while(xx>m/2)
    {
        xx=Next[xx];
    }
    int n=m-xx;
    for(int i=0;i<n;i++)
        cout<<s[i];
    for(int i=0;i<26;i++)
        ss[p[i]-'a']=(char)(i+'a');
    for(int i=0;i<n;i++)
        cout<<ss[s[i]-'a'];
    cout<<endl;
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    int _ = 1;
    cin>>_;
    while (_--) {
        init();
        solve();
    }
    return 0;
}

在给定字符串中找到最长公共子串, 或者给定字符串子串的反串;

最多100字符串,最长100,直接枚举

求反串可以用 中的reverse函数 ;

#include <bits/stdc++.h>
#include<iostream>
#include<vector>
#include<algorithm>
//#pragma GCC optimize(2)
//#pragma comment(linker, "/STACK:102400000,102400000")
using namespace std;
typedef long long ll;
const int maxn=1e6+5;
int Next[maxn];
int nex[maxn];
int m,mm;
string t,p;
string temp;
vector<string>vv,uu;
void init() {
    vv.clear();
    uu.clear();
}
bool cmp(string aa,string bb)
{
    return aa.size()>bb.size();
}
void getNext()
{
    int k=-1;
    int j=0;
    Next[j]=k;
    while(j<m)
    {
        if(k==-1||t[k]==t[j])
        {
            k++;
            j++;
            Next[j]=k;
        }
        else
            k=Next[k];
    }
}
void getnex()
{
    int k=-1;
    int j=0;
    nex[j]=k;
    while(j<m)
    {
        if(k==-1||temp[k]==temp[j])
        {
            k++;
            j++;
            nex[j]=k;
        }
        else
            k=nex[k];
    }
}
bool KMP(int start)
{
    int i=start,j=0;
    while(i<mm&&j<m)
    {
        if(j==-1||p[i]==t[j])
        {
            i++,j++;
        }
        else
            j=Next[j];
    }
    if(j==m)
        return true;
    return false;
}
bool kmp(int start)
{
    int i=start,j=0;
    while(i<mm&&j<m)
    {
        if(j==-1||p[i]==temp[j])
        {
            i++,j++;
        }
        else
            j=nex[j];
    }
    if(j==m)
        return true;
    return false;
}
void solve() {
    int n;
    cin>>n;
    string mn;
    for(int i=0;i<n;i++)
    {
        string s;
        cin>>s;
        if(i==0)
            mn=s;
        vv.push_back(s);
        if(s.size()<mn.size())
            mn=s;
    }
    for(int i=0;i<mn.size();i++)
    {
        for(int j=1;i+j<=mn.size();j++)
        {
            uu.push_back(mn.substr(i,j));
        }
    }
    sort(uu.begin(),uu.end(),cmp);
    for(int i=0;i<uu.size();i++)
    {
        int flag=1;
        t=uu[i];
        temp=t;
        reverse(temp.begin(),temp.end());
        m=t.size();
        getnex();
        getNext();
        for(int j=0;j<vv.size();j++)
        {
            p=vv[j];
            mm=p.size();
            if(KMP(0)||kmp(0))
            {

            }
            else
            {
                flag=0;
                break;
            }
        }
        if(flag==1)
        {
            cout<<m<<endl;
            return;
        }
    }
    cout<<0<<endl;
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    int _ = 1;
    cin>>_;
    while (_--) {
        init();
        solve();
    }
    return 0;
}

题目大意:给出若干个串,求最长公共子串。

#include <bits/stdc++.h>
#include<iostream>
#include<vector>
#include<algorithm>
//#pragma GCC optimize(2)
//#pragma comment(linker, "/STACK:102400000,102400000")
using namespace std;
typedef long long ll;
const int maxn=1e6+5;
int Next[maxn];
int nex[maxn];
int m,mm;
string t,p;
string temp;
vector<string>vv,uu;
void init() {
    vv.clear();
    uu.clear();
}
bool cmp(string aa,string bb)
{
    if(aa.size()==bb.size())
        return aa<bb;
    return aa.size()>bb.size();
}
void getNext()
{
    int k=-1;
    int j=0;
    Next[j]=k;
    while(j<m)
    {
        if(k==-1||t[k]==t[j])
        {
            k++;
            j++;
            Next[j]=k;
        }
        else
            k=Next[k];
    }
}
void getnex()
{
    int k=-1;
    int j=0;
    nex[j]=k;
    while(j<m)
    {
        if(k==-1||temp[k]==temp[j])
        {
            k++;
            j++;
            nex[j]=k;
        }
        else
            k=nex[k];
    }
}
bool KMP(int start)
{
    int i=start,j=0;
    while(i<mm&&j<m)
    {
        if(j==-1||p[i]==t[j])
        {
            i++,j++;
        }
        else
            j=Next[j];
    }
    if(j==m)
        return true;
    return false;
}
bool kmp(int start)
{
    int i=start,j=0;
    while(i<mm&&j<m)
    {
        if(j==-1||p[i]==temp[j])
        {
            i++,j++;
        }
        else
            j=nex[j];
    }
    if(j==m)
        return true;
    return false;
}
void solve() {
    int n;
    while(cin>>n&&n)
    {
        init();
        string mn;
        for(int i=0;i<n;i++)
        {
            string s;
            cin>>s;
            if(i==0)
                mn=s;
            vv.push_back(s);
            if(s.size()<mn.size())
                mn=s;
        }
        for(int i=0;i<mn.size();i++)
        {
            for(int j=1;i+j<=mn.size();j++)
            {
                uu.push_back(mn.substr(i,j));
            }
        }
        sort(uu.begin(),uu.end(),cmp);
        int ff=0;
        for(int i=0;i<uu.size();i++)
        {
            int flag=1;
            t=uu[i];
            m=t.size();
            getNext();
            for(int j=0;j<vv.size();j++)
            {
                p=vv[j];
                mm=p.size();
                if(KMP(0))
                {

                }
                else
                {
                    flag=0;
                    break;
                }
            }
            if(flag==1)
            {
                ff=1;
                cout<<t<<"\n";
                break;
            }
        }
        if(ff==0)
            cout<<"IDENTITY LOST"<<"\n";
    }
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    int _ = 1;
    //cin>>_;
    while (_--) {
        init();
        solve();
    }
    return 0;
}

题意 给你一个环形串 输出其最小表示法的首字母位置 最大表示法的首字母位置 以及和对应位置等价位置的个数

最小表示法指一个循环串以某一位开始时对应的串的字典序最小 这个串就是该循环串的最小表示法

先看一下求字符串最小表示法的过程 可以看2003年国家集训队论文集中周源的论文

令 p 表示字符串 s 的最小表示法的下标, l = strlen(s) s = s + s

当 i <= p && j <= p && i != j 时 令 k (0 <= k < l) 为最小使得 s[i + k] != s[j + k] 的整数

k 存在时 若 s[i + k] > s[j + k]

那么对于任意的 x (i <= x <= i + k) 都是不可能等于p的 因为对于这些 x 都有对应的 y (j <= y <= j + k) 使得 s[x, i + k) == s[y, j + k) && s[i + k] > s[j + k] 那么以 y 为起始位置的串肯定是小于以 x 为起始位置的串的 那么就可以令 i = i + k + 1继续这个过程了 s[i + k] < s[j + k] 时同理可令 j = j + k + 1 注意若前进后i == j 需要令 j = j + 1 因为要保证i != j

重复上面的过程直到 i >= l || j >= l || k不存在 此时 i, j 中小的那个数就是答案了 因为小的那个数前后所有都已经被确定不可能为 p

开始时不妨令i = 0, j = 1 p > 0 时是符合上面的条件的 p == 0 时可以发现 i 会保持0不变 j 一直前进直到 j > l 或 k 不存在

那么令 i = 0, j = 1 然后通过上面的过程就能得到最小表示法对应的位置了 同理也可以得到最大表示法对应的位置

至于求等价位置的个数 只要知道这个串是某个串循环了多少次就行了 通过kmp的l - next[l]表示最小循环节的长度就可以求出来

#include <bits/stdc++.h>
#include<iostream>
#include<vector>
#include<algorithm>
//#pragma GCC optimize(2)
//#pragma comment(linker, "/STACK:102400000,102400000")
using namespace std;
typedef long long ll;
const int maxn=1e6+5;
int Next[maxn];
int m,mm;
string t,p;
void init() {
}
void getNext()
{
    int k=-1;
    int j=0;
    Next[j]=k;
    while(j<m)
    {
        if(k==-1||t[k]==t[j])
        {
            k++;
            j++;
            Next[j]=k;
        }
        else
            k=Next[k];
    }
}
bool KMP(int start)
{
    int i=start,j=0;
    while(i<mm&&j<m)
    {
        if(j==-1||p[i]==t[j])
        {
            i++,j++;
        }
        else
            j=Next[j];
    }
    if(j==m)
        return true;
    return false;
}
int getpos(int op)///0最小表示法, 1最大表示法
{
    t=t+t;
    int i=0,j=1;
    while(i<m&&j<m)
    {
        int k=0;
        while(k<m&&t[i+k]==t[j+k])k++;
        if(k>=m)
            break;
        if((t[i+k]>t[j+k])^op)
            i+=k+1;
        else
            j+=k+1;
        if(i==j)
            j++;
    }
    return min(i,j);
}
void solve() {
    while(cin>>t)
    {
        m=t.size();
        getNext();
        int l=m-Next[m];
        int temp;
        if(m%l==0)
            temp=m/l;
        else
            temp=1;
        int L=getpos(0)+1;
        int R=getpos(1)+1;
        cout<<L<<" "<<temp<<" "<<R<<" "<<temp<<endl;
    }
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    int _ = 1;
    //cin>>_;
    while (_--) {
        init();
        solve();
    }
    return 0;
}

题目大意:

有n个有01组成的字符串,每个字符串都代表一个项链,那么该字符串就是一个环状的结构,求可以经过循环旋转,最后不同的串有多少个。

思路:把所有字符串用最小表示法表示出来,然后全部放在vector中,然后从小到大排个序
前n个有多少个不同字符就有多少个不同串,如果前n个字符全一样说明这n个都可以表示成1个

#include <bits/stdc++.h>
#include<iostream>
#include<vector>
#include<algorithm>
//#pragma GCC optimize(2)
//#pragma comment(linker, "/STACK:102400000,102400000")
using namespace std;
typedef long long ll;
const int maxn=1e6+5;
int Next[maxn];
int m,mm;
string t,p;
vector<string>uu;
void init() {
    uu.clear();
}
void getNext()
{
    int k=-1;
    int j=0;
    Next[j]=k;
    while(j<m)
    {
        if(k==-1||t[k]==t[j])
        {
            k++;
            j++;
            Next[j]=k;
        }
        else
            k=Next[k];
    }
}
bool KMP(int start)
{
    int i=start,j=0;
    while(i<mm&&j<m)
    {
        if(j==-1||p[i]==t[j])
        {
            i++,j++;
        }
        else
            j=Next[j];
    }
    if(j==m)
        return true;
    return false;
}
int getpos(int op)///0最小表示法, 1最大表示法
{
    t=t+t;
    int i=0,j=1;
    while(i<m&&j<m)
    {
        int k=0;
        while(k<m&&t[i+k]==t[j+k])k++;
        if(k>=m)
            break;
        if((t[i+k]>t[j+k])^op)
            i+=k+1;
        else
            j+=k+1;
        if(i==j)
            j++;
    }
    return min(i,j);
}
void solve() {
    int n;
    while(cin>>n)
    {
        init();
        for(int i=0;i<n;i++)
        {
            cin>>t;
            p=t;
            m=t.size();
            int res=getpos(0);
            for(int j=0;j<m;j++)
            {
                p[j]=t[(j+res)%m];
            }
            uu.push_back(p);
        }
        sort(uu.begin(),uu.end());
        uu.erase(unique(uu.begin(),uu.end()),uu.end());
        cout<<uu.size()<<endl;
    }
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    int _ = 1;
    //cin>>_;
    while (_--) {
        init();
        solve();
    }
    return 0;
}

题意:给你个字符串,让你求有多少个p可以使S[i]==S[i+P] (0<=i<len-p-1)。

题解:这个题是真的坑,一开始怎么都觉得自己不可能错,然后看了别人的博客打脸了,发现自己掉坑了了…一开始想的是找出最小循环节,只要每次输出多加一个循环节,最后输出len就好了。后来发现最小循环节的倍数并不能表示所有循环节。看到的一组例子ababaaaabab,应该输出7,9,11;但是最小循环节的输出是7,11。

#include <bits/stdc++.h>
#include<iostream>
#include<vector>
#include<algorithm>
//#pragma GCC optimize(2)
//#pragma comment(linker, "/STACK:102400000,102400000")
using namespace std;
typedef long long ll;
const int maxn=1e6+5;
int Next[maxn];
int m,mm;
string t,p;
int cas=0;
vector<string>uu;
void init() {
    uu.clear();
}
void getNext()
{
    int k=-1;
    int j=0;
    Next[j]=k;
    while(j<m)
    {
        if(k==-1||t[k]==t[j])
        {
            k++;
            j++;
            Next[j]=k;
        }
        else
            k=Next[k];
    }
}
bool KMP(int start)
{
    int i=start,j=0;
    while(i<mm&&j<m)
    {
        if(j==-1||p[i]==t[j])
        {
            i++,j++;
        }
        else
            j=Next[j];
    }
    if(j==m)
        return true;
    return false;
}
int getpos(int op)///0最小表示法, 1最大表示法
{
    t=t+t;
    int i=0,j=1;
    while(i<m&&j<m)
    {
        int k=0;
        while(k<m&&t[i+k]==t[j+k])k++;
        if(k>=m)
            break;
        if((t[i+k]>t[j+k])^op)
            i+=k+1;
        else
            j+=k+1;
        if(i==j)
            j++;
    }
    return min(i,j);
}
void solve() {
    cin>>t;
    m=t.size();
    getNext();
    int r=m-Next[m];
    int res=m/r+(m%r!=0);
    cout<<"Case #"<<cas<<": "<<res<<endl;
    int xx=r;
    res--;
    while(res)
    {
        cout<<xx<<" ";
        xx+=r;
        res--;
    }
    cout<<m;
    cout<<endl;
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    int _ = 1;
    cin>>_;
    while (_--) {
        cas++;
        init();
        solve();
    }
    return 0;
}

众所周知,语法在学习英语时非常重要。现在,YYF成为一所小学的老师。学生们善于记忆,所以能说出课本中单词的意思和功能(名词、动词等)。但是,他们不能正确使用这些词。‎

‎在YYF的心目中,写句子是学习语法的好方法。因此,他告诉学生每天写20个句子,使用在课堂上学到的单词。由于YYF有很多学生,他将会从他的学生那里获得许多句子。检查所有的句子是件多么可怕的工作啊。你是YYF的朋友之一,所以他请求你帮忙。你的任务是写一个程序来检查句子。‎

‎为了使工作简单,YYF选择了语法的一部分:所有的单词可以分为七个部分(名词、代词、形容词、副词、介词、文章和动词)。动词可以是传递的,也可以是过渡性的。因此,我们使用"n",“pron”,“adj”,“adv”,“准备”,“艺术”,“vt"和"vi"来缺少名词、词名、形容词、副词、介词、文章、转序动词和不通性动词。如果一个单词被标记为"v.”,它可以用作传递动词或不通性动词。‎

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<string>
#include<map>
using namespace std;
map<string,int> fun; //把每种词标号 
map<string,int> num; //将题目中给出的词标号 
map<string,char> str; //记录每种主语宾语谓语介词短语的每种形式,打表 
map<string,int> ste; //每种句子形式,打表 
void init()
{
	fun["n."]=0;
	fun["pron."]=1;
	fun["adj."]=2;
	fun["adv."]=3;
	fun["prep."]=4;
	fun["art."]=5;
	fun["vt."]=6;
	fun["vi."]=7;
	fun["v."]=8;
	
	str["450"]='A'; //介词短语 
	str["4520"]='A';
	str["41"]='A';
	str["1"]='S'; //主/宾语 
	str["50"]='S';
	str["520"]='S';
	str["7"]='I'; //不及物谓语 
	str["37"]='I';
	str["6"]='T'; //及物谓语 
	str["36"]='T';
	str["8"]='V'; //通用谓语 
	str["38"]='V';
	//句子可能的总体结构 
	ste["SI"]=1;
	ste["STS"]=1;
	ste["SV"]=1;
	ste["SVS"]=1;
	ste["ASI"]=1;
	ste["ASTS"]=1;
	ste["ASV"]=1;
	ste["ASVS"]=1;
	ste["SAI"]=1;
	ste["SATS"]=1;
	ste["SAV"]=1;
	ste["SAVS"]=1;
	ste["SIA"]=1;
	ste["STAS"]=1;
	ste["SVA"]=1;
	ste["SVAS"]=1;
	ste["STSA"]=1;
	ste["SVSA"]=1;
}
int n,m;
int main()
{
	string a,b;
	init();
	scanf("%d%d",&n,&m);
	while(n--)
	{
		cin>>a>>b;
		num[a]=fun[b];
	}
	while(m--)
	{
		bool flag=false;
		b="";
		while(cin>>a) //转化为词性表示 
		{
			if(isupper(a[0])) a[0]=a[0]+32;
			int len=a.length();
			if(a[len-1]=='.')
			{
				flag=true;
				a.erase(len-1,1);
			}
			if(a[len-1]==',') a.erase(len-1,1);
			b+='0'+num[a];
			if(flag) break;
		}
		//cout<<b<<endl;
		string c,d;
		c=d="";
		for(int i=0;i<b.length();i++) //转化为结构表示 
		{
			c+=b[i];
			if(str[c])
			{
				d+=str[c];
				c="";
			}
		}
		d+=c;
		//cout<<d<<endl;
		if(ste[d]) cout<<"YES"<<endl; //判断是否符合 
		else cout<<"NO"<<endl;
	}
	return 0;
}

问题在于如果快速判断a串的前缀和后缀是否是回文串,这里只说前缀(因为后缀就是反串的前缀),考虑串a与其反串b的匹配,如果a的某个前缀是回文串,那么因为其反串是b串的一个后缀,所以只要extend[i]=len-i,那么前缀a[0~i]就是一个回文串,同理,在b与a的匹配中,如果extend[i]=len-i,那么后缀a[len-1-i,len-1]就是一个回文串,做两边扩展kmp通过extend数组可以得到以第i个字符结尾或者开始的前后缀是否为回文串,之后O(n)枚举切割点更新最大价值即可(需要预处理出价值前缀和)

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
#define maxn 555555
char a[maxn],b[maxn];
int T,v[33],sum[maxn],flag1[maxn],flag2[maxn],nex[maxn],extend[maxn];
void extend_kmp(char *a,char *b) 
{
    int i,j,k,la=strlen(a),lb=strlen(b);
    for(i=0;i+1<la&&a[i+1]==a[i];i++);
    nex[1]=i;
    k=1;
    for(i=2;i<la;i++) 
    {
        int len=k+nex[k];
        nex[i]=min(nex[i-k],max(0,len-i));
        while(i+nex[i]<la&&a[nex[i]]==a[i+nex[i]])nex[i]++;
        if(i+nex[i]>k+nex[k])k=i;
    }
    for(i=0;i<la&&i<lb&&a[i]==b[i];i++);
    extend[0]=i;
    k=0;
    for(i=1;i<lb;i++) 
    {
        int len=k+extend[k];
        extend[i]=min(nex[i-k],max(0,len-i));
        while(i+extend[i]<la&&i+extend[i]<lb&&a[extend[i]]==b[extend[i]+i])
            extend[i]++;
        if(k+extend[k]<i+extend[i])k=i;
    }
}
int main()
{   
    scanf("%d",&T);
    while(T--)
    {
        memset(flag1,0,sizeof(flag1));
        memset(flag2,0,sizeof(flag2));
        for(int i=0;i<26;i++)scanf("%d",&v[i]);
        scanf("%s",a);
        int len=strlen(a);
        for(int i=0;i<len;i++)b[i]=a[len-1-i];
        sum[0]=v[a[0]-'a'];
        for(int i=0;i<len;i++)sum[i]=sum[i-1]+v[a[i]-'a'];
        extend_kmp(a,b);
        for(int i=0;i<len;i++)if(extend[i]==len-i)flag1[len-1-i]=1;
        extend_kmp(b,a);
        for(int i=0;i<len;i++)if(extend[i]==len-i)flag2[i]=1;
        int ans=v[a[0]-'a'];
        for(int i=0;i<len-1;i++)
        {
            int temp=flag1[i]*sum[i]+flag2[i+1]*(sum[len-1]-sum[i]);
            ans=max(ans,temp);
        }
        printf("%d\n",ans);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值