有一个很显然的 DP, 设 (f[i]) 表示前 (i) 考虑完了前 (i) 个字符的最小代价.
有两种转移:
- (f[i]=f[i-1]+c[s_i]).
- (f[i]=min_{d_i-1le j<i}{f[j]+calc(j+1,i)}).
(d_i) 为最小的满足 (([d_i,i]) 是 ([1,d_{i}-1]) 的子串) 的位置, 可以用 SAM 预处理出来.
具体来说, 将字符 (s_i) 依次加入, 维护一个指针表示当前最大的 (p) 使得 ([i+1,p]) 是 ([1,i]) 的子串.
然后发现第 2 种转移可以用单调队列优化, 就完了. 复杂度为 (O(N)).
</:>
#include <bits/stdc++.h>
#define N 202200
using namespace std;
int sze, las, lnk[N << 1], len[N << 1], nxt[N << 1][10];
void extend(int c) {
int cur = ++sze, p; cur[len] = las[len] + 1;
for (p = las; p && !nxt[p][c]; p = lnk[p])
nxt[p][c] = cur;
if (!p) lnk[cur] = 1;
else {
int q = nxt[p][c];
if (len[q] == len[p] + 1) lnk[cur] = q;
else {
int cln = ++sze; cln[len] = p[len] + 1;
memcpy(nxt[cln], nxt[q], sizeof nxt[q]);
lnk[cln] = lnk[q], lnk[q] = lnk[cur] = cln;
for (; p && nxt[p][c] == q; p = lnk[p])
nxt[p][c] = cln;
}
} las = cur;
}
int n, a, b, c[12], d[N];
long long f[N];
char s[N];
inline int go(int p, int ch, int le) {
while (p != 1 && !nxt[p][ch])
p = lnk[p], le = len[p];
if (nxt[p][ch]) p = nxt[p][ch], ++le;
return le;
}
int main() {
scanf("%s", s + 1);
n = strlen(s + 1);
for (int i = 0; i <= 9; ++i)
scanf("%d", c + i);
cin >> a >> b;
las = sze = 1;
int p = 1, t = 0, le = 0;
for (int i = 1, j = 1; i <= n; ++i) {
extend(s[i] - '0');
while (t < n && go(p, s[t + 1] - '0', le) >= t + 1 - i) {
int ch = s[++t] - '0';
while (p != 1 && !nxt[p][ch])
le = len[p = lnk[p]];
if (nxt[p][ch]) p = nxt[p][ch], ++le;
}
while (j <= t) d[j++] = i + 1;
}
static int q[N], ql = 1, qr = 0;
for (int i = 1; i <= n; ++i) {
f[i] = f[i - 1] + c[s[i] - '0'];
while (ql <= qr && q[ql] < d[i] - 1) ++ql;
if (ql <= qr) {
int j = q[ql];
f[i] = min(f[i], f[j] + 1ll * a * (i - j) + b);
}
while (ql <= qr && (f[i] - 1ll * a * i) <= (f[q[qr]] - 1ll * a * q[qr])) --qr;
q[++qr] = i;
}
cout << f[n] << endl;
return 0;
}