b_lg_最长前缀(kmp+dfs / 优化dp)

如果一个集合P中的元素可以串起来(元素可以重复使用)组成一个序列s ,那么我们认为序列s可以分解为P中的元素。元素不一定要全部出现。举个例子,序列ABABACABAAB可以分解为下面集合中的元素:{A,AB,BA,CA,BBC} (BBC就没有出现)。求S符合条件的前缀的最大长度

kmp思路:kmp求出每个段p[i]在s中出现的起始、结束位置,求完以后从0开始dfs枚举数组p能构成s的最长前缀

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5, M=205;
int tot,nx[20],ans,vis[N];
string s,t[M],tmp;
vector<int> pos[N];
void get_nx(string& t) {
    for (int i=2,j=0,n=t.size()-1; i<=n; i++) {
        while (j && t[i]!=t[j+1]) j=nx[j];
        if (t[i]==t[j+1]) j++;
        nx[i]=j;
    }
}
void kmp(string& s, string& t) {
    int n=s.size()-1, m=t.size()-1;
    for (int i=1,j=0; i<=n; i++) {
        while (j && s[i]!=t[j+1]) j=nx[j];
        if (s[i]==t[j+1]) j++;
        if (j==m) {
            pos[i-j].push_back(i); //i看能和j相同,所以dfs从0开始
            j=nx[j];
        }
    }
}
void dfs(int i) { //i是s中的开头
    vis[i]=1;
    if (pos[i].empty() && i>ans) ans=i;
    for (int nx_p : pos[i]) if (!vis[nx_p]) {
        dfs(nx_p);
    }
}
int main() {
    std::ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    while (cin>>tmp && tmp!=".") t[tot++]="/"+tmp;
    while (cin>>tmp) s+=tmp;
    s="/"+s;
    for (int i=0; i<tot; i++) {
        memset(nx,0,sizeof nx);
        get_nx(t[i]);
        kmp(s,t[i]);
    }
    dfs(0);
    cout<<ans;
    return 0;
}

发现有更优的线性dp解法:f[i]表示s的长度为i的前缀能否用p中的字符串组成
暴力串匹配是对于每一个串都截取s长度相同的子串来比较,优化方法是:用数组len[i]来存储第i个片段的长度,先比较片段长度,再比较内容

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5, M=205;
int n,m,f[N],len[M];
string s,t[M],tmp;
int main() {
    std::ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    while (cin>>tmp && tmp!=".") t[++m]=tmp, len[m]=tmp.size();
    while (cin>>tmp) s+=tmp;
    n=s.size(), f[0]=1;
    for (int i=1; i<=n; i++)
    for (int j=1; j<=m; j++) {
        if (i>=len[j] && f[i-len[j]] && s.substr(i-len[j], len[j])==t[j]) {
            f[i]=1;
        }
    }
    for (int i=n; ~i; i--) if (f[i])
        return cout<<i, 0;
    return 0;
}
原文地址:https://www.cnblogs.com/wdt1/p/13867696.html