字符串(KMP , Trie树 , STL)

kmp算法用于优化字符串匹配效率:

 

//KMP字符串匹配:
//模板:
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
char s1[N],s2[N];
int ne[N],n,res;
int main()
{
    std::ios::sync_with_stdio(false);
    cin>>s1+1>>s2+1;
    int len1=strlen(s1+1),len2=strlen(s2+1);
    for(int i=2,j=0;i<=len2;i++){
        while(j&&s2[i]!=s2[j+1]) j=ne[j];
        if(s2[i]==s2[j+1]) j++;
        ne[i]=j;
    }
    for(int i=1,j=0;i<=len1;i++){
        while(j&&s1[i]!=s2[j+1]) j=ne[j];
        if(s1[i]==s2[j+1]) j++;
        if(j==len2) cout<<i-j+1<<endl,j=ne[j];
    }
    for(int i=1;i<=len2;i++) cout<<ne[i]<<" ";
    return 0;
}

 

//test:https://www.luogu.com.cn/problem/CF25E
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10,INF=2147483647;
long long ne[4][N],len[4],k[4][4],res=INF;//ne是next数组,第i个的next数组,k是字符串是否匹配以及最大匹配的长度
char s[4][N];
bool vis[4];
void get_next(int l,char tmp[],int num)//预处理next数组,二维表示
{
    for(int i=2,j=0;i<=l;i++)
    {
        while(j&&tmp[i]!=tmp[j+1]) 
            j=ne[num][j];
        if(tmp[i]==tmp[j+1]) 
            j++;
        ne[num][i]=j;
    }
}
int kmp(int len1,int len2,char s1[],char s2[],int num)//KMP处理匹配数据
{
    int j=0;
    for(int i=1;i<=len1;i++)
    {
        while(j&&s1[i]!=s2[j+1]) 
            j=ne[num][j];
        if(s1[i]==s2[j+1])
            j++;
        if(j==len2){
            return -1;
            j=ne[num][j];
        }
    }
    return j;
}
int main()
{
    std::ios::sync_with_stdio(false);
    cin>>s[1]+1>>s[2]+1>>s[3]+1;
    for(int i=1;i<=3;i++)
    {
        len[i]=strlen(s[i]+1);
        get_next(len[i],s[i],i);
    }
    for(int i=1;i<=3;i++){
        for(int j=1;j<=3;j++)
        {
            if(i==j) continue;
            k[i][j]=kmp(len[i],len[j],s[i],s[j],j);
        }
    }
    for(int i=1;i<=3;i++)
    {
        for(int j=1;j<=3;j++)
        {
            for(int op=1;op<=3;op++)//分情况讨论
            {
                if(i==j||j==op||i==op) continue;
                long long sum=len[i]+len[j]+len[op]-k[i][j]-k[j][op];
                if(k[i][j]>=0&&k[j][op]>=0) //如果j和op都不是i的子串,那就是他们相加
                    res=min(res,sum);
                else
                {
                    if(k[i][j]<0&&k[i][op]<0)//如果两者都是子串,那就是i的长度
                        res=min(res,len[i]);
                    else if(k[i][j]<0)//如果只有j是子串,那就删去j
                        res=min(res,len[i]+len[op]-k[i][op]);
                    if(k[j][op]<0)//如果op是j的子串,那就删去op
                        res=min(res,sum+k[j][op]-len[op]);
                }
            }
        }
    }
    cout<<res<<endl;
    return 0;
}

 

//[BOI2009]Radio Transmission 无线传输:https://www.luogu.com.cn/problem/P4391
//对next数组更深刻的理解,如果周期性变化,那么n-next[n]就是最短周期
//因为next数组不可能储存第一个周期出现的次数,当地一个周期出现后,之后的都是以周期出现
//next数组会不断更新
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
char s[N];
int ne[N],res,len,n;
int main()
{
    std::ios::sync_with_stdio(false);
    cin>>n>>s+1;
    for(int i=2,j=0;i<=n;i++){
        while(j&&s[i]!=s[j+1]) j=ne[j];
        if(s[i]==s[j+1]) j++;
        ne[i]=j;
    }
    cout<<n-ne[n];
    return 0;
}
//魔族密码:https://www.luogu.com.cn/problem/P1481
//字符串的最长上升子序列
//find函数返回的是第一个子串的第一个位置,如果没找到就是返回-1
//如果find==0,也就是从第一个字母开始就是匹配的,这种可以被分为一类
#include<bits/stdc++.h>
using namespace std;
const int N=2050;
int n,res,f[N];
string s[N];
int main()
{
    cin>>n;
    for(int i=0;i<n;i++) cin>>s[i];
    for(int i=0;i<n;i++){
        f[i]=1;
        for(int j=0;j<i;j++){
            if(s[i]>s[j]&&s[i].find(s[j])==0)
                f[i]=max(f[i],f[j]+1);
        }
    }
    for(int i=0;i<n;i++) res=max(res,f[i]);
    cout<<res;
    return 0;
}

 

//[POI2006] OKR-Periods of Words:https://www.luogu.com.cn/problem/P3435
//题意就是求每个子串的最小公共前后缀,也就是让我们的next数组缩到最小就可以
//既然要求最小公共前后缀,由于我们的next数组是求每一段子串最大的公共前后缀
//那么我们只需要不断的缩小next数组,直到next数组为0,为0的位置就是两者第一个公共前后缀的位置
//这里要记忆化一下,枚举到i的时候可以直接跳到j,减少枚举次数
#include <bits/stdc++.h>
//#define int long long
using namespace std;
const int N=1e6+10,mod=1e9+7;
string s;
char s1[N];
long long n,t,a[N],f[N],res,num,ans,ne[N];
bool vis[N];
int main()
{
    std::ios::sync_with_stdio(false);
    cin>>n>>s1+1;
    for(int i=2,j=0;i<=n;i++){
        while(j&&s1[i]!=s1[j+1]) j=ne[j];
        if(s1[i]==s1[j+1]) j++;
        ne[i]=j;
    }
    for(int i=1;i<=n;i++){
        int j=i;
        while(ne[j]) j=ne[j];
        cout<<i<<" "<<j<<endl;
        res+=i-j;
        if(ne[i]) ne[i]=j;
    }
    cout<<res;
    return 0;
}

 

 

 

Trie树: 0-1树

 

//Trie树:高效查找和储存字符串,构成集合:
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int son[N][26],cnt[N],idx,n;//当前节点存的子节点,某个字母出现的次数,标记
void Insert(char str[])//插入树操作
{
    int p=0;
    for(int i=0;str[i];i++){
        int u=str[i]-'a';
        if(!son[p][u]) son[p][u]=++idx;//如果当前没有储存节点,那就建立一个节点
        p=son[p][u];//向下移动
    }
    cnt[p]++;//这个字符串+1;
}
int query(char str[])
{
    int p=0;
    for(int i=0;str[i];i++){
        int u=str[i]-'a';
        if(!son[p][u]) return 0;//如果没找到
        p=son[p][u];//找到了,继续向下遍历
    }
    return cnt[p];
}
int main()
{
    cin>>n;
    while(n--){
        char op[2],s[N];
        cin>>op>>s;
        if(op[0]=='I') Insert(s);
        else cout<<query(s)<<endl;
    }
    return 0;
}

 

//于是他错误的点名开始了:https://www.luogu.com.cn/problem/P2580
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int son[N][26],idx,cnt[N],n,t;
char s[N],tmp[N];
map<string,int>mp;
void insert(char str[])
{
    int p=0;
    for(int i=0;str[i];i++){
        int u=str[i]-'a';
        if(!son[p][u]) son[p][u]=++idx;
        p=son[p][u]; 
    }
    cnt[p]++;
}
bool check(char str[])
{
    int p=0;
    for(int i=0;str[i];i++){
        int u=str[i]-'a';
        if(!son[p][u]) return false;
        p=son[p][u];
    }
    if(cnt[p]) return true;//借此判断
    else return false;
}
int main()
{
    cin>>n;
    for(int i=0;i<n;i++) cin>>s,insert(s);
    cin>>t;
    while(t--){
        cin>>tmp;
        if(mp.count(tmp)) cout<<"REPEAT"<<endl;
        else{
            if(check(tmp)){
                cout<<"OK"<<endl;
                mp[tmp]=1;
            }
            else cout<<"WRONG"<<endl;
        }
    }
    return 0;
}
//[USACO2.3]最长前缀 Longest Prefix:https://www.luogu.com.cn/problem/P1470
//方法一:KMP + dp
//利用kmp匹配检查当前子串是否合法,最后枚举子串中的子串,确定最长子串
#include<bits/stdc++.h>
using namespace std;
const int N=3e5+10;
string s="#",p[220];
int res,n,m,ne[220][15],len[220],c;
bool vis[220][N],f[N];//vis代表i这个字符串的前j位是否合法,f代表长度i的字符串是否合法
void get_next(int num,string s2)
{
    vis[num][1]=0;
    for(int i=2,j=0;i<len[num];i++){
        while(j&&s2[i]!=s2[j+1]) j=ne[num][j];
        if(s2[i]==s2[j+1]) j++;
        ne[num][i]=j;
    }
}
void KMP(string s1,int num,string s2)
{
    for(int i=1,j=0;i<=s1.size();i++){
        while(j&&s1[i]!=s2[j+1]) j=ne[num][j];
        if(s1[i]==s2[j+1]) j++;
        if(j==len[num]){
            vis[num][i]=true;
            j=ne[num][j];
        }
    }
}
int main()
{
    while(cin>>p[++c]&&p[c]!="."){
        len[c]=p[c].length();
        p[c]='#'+p[c];//向前添一位,从一开始
    }
    c--;
    string x;
    while(cin>>x) s=s+x;
    for(int i=1;i<=c;i++){
        get_next(i,p[i]);
        KMP(s,i,p[i]);
    }
    f[0]=true;
    for(int i=1;i<=s.size();i++){
        for(int j=1;j<=c;j++)
            if(vis[j][i]&&(f[i]||f[i-len[j]])) f[i]=true;
    }
    for(int i=s.size();i>=0;i--)
        if(f[i]){
            cout<<i;
            break;
        }
    return 0;
}
//方法二:set + dp
//:规划的是s的状态,每次都看当前子串减去某一子串之后,该子串是否在集合中出现,是否合法
//f用于是不是合法,从前向后枚举每一子串
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int f[N],res,n,maxx;
string ss,tmp=" ";
set<string>s[20];
int main()
{
    while(cin>>ss&&ss!="."){
        s[ss.size()].insert(ss);
        maxx=max(maxx,(int)ss.size());
    }
    while(cin>>ss) tmp=tmp+ss;
    f[0]=1;
    for(int i=1;i<tmp.size();i++){
        for(int j=min(i,maxx);j>=1;j--){
            string now=tmp.substr(i-j+1,j);
            if(s[now.size()].count(now)==1&&f[i-j]==1){
                f[i]=1;
                res=i;
                break;
            }
        }
    }
    cout<<res;
    return 0;
}

 

posted @ 2023-06-18 20:34  o-Sakurajimamai-o  阅读(47)  评论(0)    收藏  举报
-- --