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;
}