题目
内容
给了n个单词, s 1 , s 2 , s 3 . . . s_1,s_2,s_3... s1,s2,s3...,每个单词有一定的代价.用这些单词组成给定的文本串T,求代价和最小.
∑ i = 1 n ∣ s i ∣ ≤ 5 e 5 \sum_{i=1}^n |s_i|\le5e^5 ∑i=1n∣si∣≤5e5, ∣ T ∣ ≤ 5 e 5 |T|\le5e^5 ∣T∣≤5e5
分析
这题的重点就是限制了字符串的总长度.
我们定义match数组:match[i]为i节点的最长后缀,并且这个后缀是n个单词中的一个.如果不存在这样的后缀那么match[i]为AC自动机的根节点.
由于长度不同的字符串不会超过 ∑ i n ∣ s i ∣ \sqrt{\sum_{i}^n|s_i|} ∑in∣si∣ ,因此AC自动机上用match数组转移时不会超过 ∑ i n ∣ s i ∣ \sqrt{\sum_{i}^n|s_i|} ∑in∣si∣次,也许你并没理解,详细解释一下.
首先, 1 + 2 + 3 + . . . + n = ( n + 1 ) ∗ n 2 1+2+3+...+n=\frac{(n+1)*n}{2} 1+2+3+...+n=2(n+1)∗n,因此如果总长度为 l e n len len,那么字符串的个数不会超过 l e n \sqrt{len} len
其次match数组所指向的节点是当前的节点的一个后缀,所以长度必然比当前节点小.
结合起来就是AC自动机的每一个节点最多通过 l e n \sqrt{len} len次转移就会到根节点.
(最坏情况的一个例子:a,aa,aaa,aaaa,…)
如何求match数组:如果fail[i]为一个单词的结尾,那么match[i]=fail[i],否则match[i]=match[fail[i]].
接下就是一个简单的dp了: d p [ i ] dp[i] dp[i]表示匹配到 i i i个字符的最小代价.
复杂度 O ( n n ) O(n\sqrt n) O(nn)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
typedef vector<int> vi;
#define debug(x) cerr<<#x<<' '<<x<<'\n'
#define mp make_pair
#define pb push_back
#define fi first
#define se second
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define per(i,a,b) for(int i=(a);i>=(b);i--)
const int maxn=6e5+10;
const int mod=1e9+7;
const int inf=0x3f3f3f3f;
ll dp[maxn];
struct Trie {
int next[maxn][26],fail[maxn],root,cnt,len[maxn];
int match[maxn];
ll p[maxn];
int newnode() {
rep(i,0,25) next[cnt][i]=-1;
len[cnt]=0;
p[cnt++]=0;
return cnt-1;
}
void init() {
cnt=0;
root=newnode();
}
void ins(char a[],ll x) {
int lena=strlen(a),now=root;
rep(i,0,lena-1) {
if(next[now][a[i]-'a']==-1)
next[now][a[i]-'a']=newnode();
len[next[now][a[i]-'a']]=len[now]+1;
now=next[now][a[i]-'a'];
}
if(p[now]==0) p[now]=x;
else p[now]=min(p[now],x);
}
void build() {
queue<int> q;
fail[root]=root;
rep(i,0,25) {
if(next[root][i]==-1)
next[root][i]=root;
else {
fail[next[root][i]]=root;
match[next[root][i]]=root;
q.push(next[root][i]);
}
}
while(!q.empty()) {
int now=q.front();
q.pop();
rep(i,0,25) {
if(next[now][i]==-1) next[now][i]=next[fail[now]][i];
else {
fail[next[now][i]]=next[fail[now]][i];
q.push(next[now][i]);
int tmp=fail[next[now][i]];
if(p[tmp]) match[next[now][i]]=tmp;
else match[next[now][i]]=match[tmp];
}
}
}
}
ll query(char buf[]) {
int lenbuf=strlen(buf),now=root;
rep(i,0,lenbuf-1) {
now=next[now][buf[i]-'a'];
for(int t=now;t;t=match[t]) {
if(p[t]!=0) {
if(dp[i+1-len[t]]!=0||i+1-len[t]==0) {
if(dp[i+1]==0) dp[i+1]=dp[i+1-len[t]]+p[t];
else dp[i+1]=min(dp[i+1],dp[i+1-len[t]]+p[t]);
}
}
}
}
return dp[lenbuf];
}
}AC;
char str[maxn];
ll pr;
int main()
{
ios::sync_with_stdio(false);cin.tie(0);
int n;
cin>>n;
AC.init();
rep(i,1,n) {
cin>>str>>pr;
AC.ins(str,pr);
}
AC.build();
cin>>str;
ll ans=AC.query(str);
if(ans==0) cout<<-1<<'\n';
else cout<<ans<<'\n';
return 0;
}
本文探讨了使用AC自动机和动态规划(DP)解决字符串匹配问题的方法,特别是在大规模文本中寻找最小代价路径的场景。文章详细介绍了AC自动机的构建过程,包括如何通过match数组优化状态转移,以及如何利用DP求解最优解。复杂度分析显示,该方法在特定条件下具有高效性。

713

被折叠的 条评论
为什么被折叠?



