怎么想都是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;
}