破忒头的匿名信-AC自动机-dp

本文探讨了使用AC自动机和动态规划(DP)解决字符串匹配问题的方法,特别是在大规模文本中寻找最小代价路径的场景。文章详细介绍了AC自动机的构建过程,包括如何通过match数组优化状态转移,以及如何利用DP求解最优解。复杂度分析显示,该方法在特定条件下具有高效性。

题目

题目链接

内容

给了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=1nsi5e5, ∣ T ∣ ≤ 5 e 5 |T|\le5e^5 T5e5

分析

这题的重点就是限制了字符串的总长度.

我们定义match数组:match[i]为i节点的最长后缀,并且这个后缀是n个单词中的一个.如果不存在这样的后缀那么match[i]为AC自动机的根节点.

由于长度不同的字符串不会超过 ∑ i n ∣ s i ∣ \sqrt{\sum_{i}^n|s_i|} insi ,因此AC自动机上用match数组转移时不会超过 ∑ i n ∣ s i ∣ \sqrt{\sum_{i}^n|s_i|} insi 次,也许你并没理解,详细解释一下.

首先, 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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值