【HDU】5470 Typewriter 【后缀自动机+dp】

传送门:【HDU】5470 Typewriter

首先暴力的方法就是,对第i个位置,我枚举j(j < i),然后看子串[j+1…i]是不是在[1…j]中存在(假设串从下标1开始),然后dp[i]=min{dp[i-1]+cost[s[i]],dp[j]+(i-j)*A+2*B)},首先我们可以找到一个最小的合法的j,这样从j~i的这个区间内全部都是合法的,这样我们就可以直接在区间[j,i]内寻找最小值即可,可以用后缀数组(SA)求lcp来实现O(logn)找到最小的j,然后区间查询最小值,外加单点更新,这样整道题可以在O(NlogN)的时间复杂度内实现。
幸运的是,区间求最小值不需要线段树(省去一些编程复杂度)。因为当i->i+1时,j是单调不减的!因为如果在添加第i+1个字符时j减小了,那么在第i个字符的时候我们就可以减小,矛盾。这样我们就可以用单调队列来求区间最小值了(单调队列求最小值的原理是维护一个递增的队列,当枚举的左端点大于队列最左端表示的点,就把队列最左端的点丢弃,加入一个点就维护这个队列,使得维持递增这个性质。最后区间最小值就是队列最左端的值)。
现在我们要做的是,将算法复杂度优化到O(N),这里我选择的是后缀自动机(SAM)代替后缀数组(SA)(其实我也只会SAM和SA……)。SAM的具体操作是,假设第i-1个位置处理完,开始枚举第i个字符时,假设上一个合法的最小值为j(注意这时候只添加了1~j-1内的字符,合法位置内的字符不添加进SAM),[j…i-1]表示的串在SAM中的表示的节点为point,现在我们判断[j…i]是否在SAM中,即point时候有后继s[i],如果有那么j不变,否则将s[j]加入SAM,然后++j,并且point调到他的最长后缀。重复之前的判断直到j==i或者point有后继s[i]。接下来只要套上上面的单调队列求区间最小值即可。

PS:SAM上的某一个节点的最长后缀(非本身),不一定是fail,可能是自身,其跳到自身的意义就是换了一条能通过后继到达他的边。这个是我今天做这题入坑后写了暴力对拍才找出的错误……SAM包括这题总共也只做过3题……以前就知道理论BB,知道怎么做了就扔给队友<_<

my  code:

#include <bits/stdc++.h>
using namespace std ;

typedef long long LL ;

#define clr( a , x ) memset ( a , x , sizeof a )

const int MAXN = 100005 ;
const int N = 26 ;

namespace Suffix_Automation {

    struct Node {
        Node* nxt[N] ;
        Node* fail ;
        int l ;
        int c ;

        void newnode ( int _l , int _c ) {
            clr ( nxt , 0 ) ;
            fail = NULL ;
            l = _l ;
            c = _c ;
        }
    } ;

    Node node[MAXN << 1] ;
    Node* last ;
    Node* root ;
    Node* cur ;
    Node* point ;

    void init () {
        point = root = last = cur = node ;
        cur->newnode ( 0 , -1 ) ;
    }

    void create ( Node* p , Node* np , int c ) {
        Node *q = p->nxt[c] , *nq = ++ cur ;
        *nq = *q ;
        nq->l = p->l + 1 ;
        q->fail = nq ;
        if ( point == q ) point = nq ;
        if ( np != NULL ) np->fail = nq ;
        for ( ; p && p->nxt[c] == q ; p = p->fail ) p->nxt[c] = nq ;
    }

    void add ( int c ) {
        Node *p = last , *np = last = ++ cur ;
        np->newnode ( p->l + 1 , c ) ;
        for ( ; p && p->nxt[c] == NULL ; p = p->fail ) p->nxt[c] = np ;
        if ( p == NULL ) np->fail = root ;
        else if ( p->l + 1 == p->nxt[c]->l ) np->fail = p->nxt[c] ;
        else create ( p , np , c ) ;
    }
} ;

using namespace Suffix_Automation ;
char s[MAXN] ;
int c[N] ;
LL dp[MAXN] , A , B ;
pair < LL , int > Q[MAXN] ;
int head , tail ;
int n ;

void solve () {
    scanf ( "%s" , s + 1 ) ;
    n = strlen ( s + 1 ) ;
    for ( int i = 0 ; i < N ; ++ i ) scanf ( "%d" , &c[i] ) ;
    scanf ( "%lld%lld" , &A , &B ) ;
    init () ;
    head = tail = 0 ;
    int l = 0 ;
    for ( int i = 1 , j = 0 ; i <= n ; ++ i ) {
        s[i] -= 'a' ;
        dp[i] = dp[i - 1] + c[s[i]] ;
        while ( j + 1 < i && !point->nxt[s[i]] ) {
            add ( s[++ j] ) ;
            if ( point != root && -- l == point->fail->l ) point = point->fail ;
        }
        if ( point->nxt[s[i]] ) point = point->nxt[s[i]] , ++ l ;
        else add ( s[++ j] ) , point = root , head = tail = 0 ;
        while ( head != tail && j > Q[head].second ) ++ head ;
        if ( head != tail ) dp[i] = min ( dp[i] , Q[head].first + i * A + B + B ) ;
        Q[tail ++] = make_pair ( dp[i] - i * A , i ) ;
        while ( head + 1 != tail && Q[tail - 1].first <= Q[tail - 2].first ) {
            -- tail ;
            Q[tail - 1] = Q[tail] ;
        }
    }
    printf ( "%lld\n" , dp[n] ) ;
}

int main () {
    int T ;
    scanf ( "%d" , &T ) ;
    for ( int i = 1 ; i <= T ; ++ i ) {
        printf ( "Case #%d: " , i ) ;
        solve () ;
    }
    return 0 ;
}

你可能感兴趣的:(【HDU】5470 Typewriter 【后缀自动机+dp】)