【笔记】线头 DP/【题解】[BalticOI2013] Vim
线头 DP 可以理解为线性插头 DP。
对于这道题,我们先将问题转化一下,将所有 \(\texttt{"e"}\) 去掉,问题转化为每次向后面跳若干格子,图中不能经过与跳跃结束的格子相同的位置,或者向前移动一格,标记了一些必须经过的格子,求代价最小的路径。
显然是每次后跳若干次,然后向前移动一段,向前移动的段不相交。转化到这一步都不困难。
然后我们可以直接 DP,记录 \(f_{i,j}\) 表示最后一段向前的段是 \(i \to j\),可以简单做到 \(\mathcal{O}(N^3)\),优化一下可以做到 \(\mathcal{O}(N^2|S|)\),能拿到 \(60\) 分。
考虑用线头 DP,我们一次考虑 \(i\sim i + 1\) 之间这一段被经过了几次,只可能被经过了 \(1\) 次或者 \(3\) 次,\(f_{i,x}\) 表示经过 \(1\) 次,前 \(i\) 个位置,最后面的插头为 \(x\) 的最小代价,同理 \(g_{i,x,y}\) 表示经过 \(3\) 次,对应的两个插头分别为 \(x,y\) 的最小代价。时间复杂度 \(\mathcal{O}(N|S|^2)\)。
#define N 70005
int n, m, f[N][10], g[N][10][10], u[N], v[N]; char s[N];
int main() {
read(n), scanf("%s", s + 1);
rp(i, n)if(s[i] != 'e'){
u[++m] = s[i] - 'a';
if(s[i - 1] == 'e')v[m] = 1;
}
memset(f, 0x3f, sizeof(f));
memset(g, 0x3f, sizeof(g));
rep(i, 0, 9)f[1][i] = 2;
rep(i, 0, m - 1){
int op = u[i + 1];
rep(j, 0, 9){
if(j != op){
if(!v[i + 1])cmn(f[i + 1][j], f[i][j]);
cmn(f[i + 1][j], g[i][op][j]);
}
cmn(f[i + 1][j], f[i][op] + 2);
cmn(f[i + 1][j], g[i][op][op] + 2);
rep(k, 0, 9){
if(k != op && j != op)cmn(g[i + 1][j][k], g[i][j][k] + 1);
if(k != op)cmn(g[i + 1][j][k], g[i][op][k] + 3);
if(j != op)cmn(g[i + 1][j][k], g[i][j][op] + 3);
cmn(g[i + 1][j][k], g[i][op][op] + 5);
if(j != op)cmn(g[i + 1][j][k], f[i][j] + 3);
cmn(g[i + 1][j][j], f[i][op] + 5);
}
}
}
cout << f[m][4] - 2 + (n - m) * 2 << endl;
return 0;
}