2019HDU多校第一场 1006.Typewriter(后缀自动机+dp)

怎么想都是HDU 5470 Typewriter改的…(思路一样,连题目都一样…)
dp式非常好想,当前dp[i]就等于从上一个位置支付p代价和能复制的最前位置支付q代价中取min。

问题就在于怎么搞到复制的转移位置。

对整串建sam,然后把串再放到自己的sam上跑,做一个匹配,并且记录匹配长度。
但是最长匹配位置并不一定合法,有可能过长(也就是说,两个子串重复出现是有交叉的,比如ababab,abab出现了两次,但是这两次存在交叉,题目要求的复制必不能交叉)。
由于sam,每个节点代表的最大长度是单调的,解决这个问题只要往前跳即可,跳到一个没有与当前位置交叉的节点即可。
复杂度大抵是均摊O(n)的。

题解给出了一种边跑边建sam的做法,比上面写的这种更加优秀,常数更小。保持sam的最小化,失配的时候向后继续建sam,如果建到了当前位置还无法满足复制条件,那么就只能通过支付p转移,如果中途就能够匹配了,那一定是一个最长的合适位置。

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int maxn = 2e5 + 5;

int p, q;
char s[maxn];

struct Sam {
    int next[maxn << 1][26];
    int link[maxn << 1], step[maxn << 1];
    int id[maxn << 1];
    ll dp[maxn];
    int sz, last, root;

    void init() {
        //如多次建立自动机,加入memset操作
        root = sz = last = 1;
        memset(next[root], 0, sizeof(next[root]));
    }

    void add(int c, int x) {
        int p = last;
        int np = ++sz;
        last = np;

        memset(next[np], 0, sizeof(next[np]));
        step[np] = step[p] + 1;
        id[np] = x;
        while (!next[p][c] && p) {
            next[p][c] = np;
            p = link[p];
        }

        if (p == 0) {
            link[np] = root;
        } else {
            int q = next[p][c];
            if (step[p] + 1 == step[q]) {
                link[np] = q;
            } else {
                int nq = ++sz;
                memcpy(next[nq], next[q], sizeof(next[q]));
                step[nq] = step[p] + 1;
                id[nq] = id[q];
                link[nq] = link[q];
                link[q] = link[np] = nq;
                while (next[p][c] == q && p) {
                    next[p][c] = nq;
                    p = link[p];
                }
            }
        }
    }

    void build() {
        init();
        for (int i = 1; s[i]; i++) {
            add(s[i] - 'a', i);
        }
    }

    void solve() {
        int len = strlen(s + 1);

        int now = root, cnt = 0;
        dp[1] = p;
        for (int i = 2; i <= len; ++i) {
            dp[i] = dp[i - 1] + p;
            while (!next[now][s[i] - 'a'] && now != root) {
                now = link[now];
                cnt = step[now];
            }
            now = next[now][s[i] - 'a'], ++cnt;
            while (i - id[now] < cnt && now != root) {
                now = link[now];
                cnt = step[now];
            }
            dp[i] = min(dp[i], dp[i - cnt] + q);
        }
        printf("%lld\n", dp[len]);
    }
} sam;

int main() {
    while (~scanf("%s%d%d", s + 1, &p, &q)) {
        sam.build();
        sam.solve();
    }
    return 0;
}
发布了156 篇原创文章 · 获赞 20 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/Cymbals/article/details/97117483