Loj #2687 - 「BalticOI 2013」Vim
搞了一个 \(nk^2\) 的做法。
首先如果前面是一个 \(e\) 那下一步一定是将 \(e\) 删掉,花费 \(2\) 。
那么可以将 \(e\) 删掉,每个之前有 \(e\) 的点都必须经过,求最短的方案。
答案一定是向后走一段,然后把前面的所有必经点都走掉,于是可以轻松得到 \(O(n^2)\) 的做法。
现在考虑优化。
很容易想到的是每次只向后走一个,然后立即将前面所有必经点走掉。如果这个结论正确那状态是 \(nk\) 的,很容易转移。
然而需要注意这里向后走的费用是 \(2\),于是类似:
这样的数据可以卡掉。
但是我们可以考虑一个更平凡的结论:
- 考虑每次向后一段再向前,再向后一次,最终到达的那一个节点一定是第一次到达。
证明:例如:
\[\begin{matrix} a & \to & \to & b &\to & c \\ & d & \leftarrow &\leftarrow &\leftarrow&\leftarrow \\ & & \to &\to &e \end{matrix} \]我们显然可以改为:
\[\begin{matrix} a & \to & \to & b & & c \\ & d & \leftarrow & && \\ & \to & \to &\to &e \\ & & & b+1 & \leftarrow \\ & & & \to& \to & c \end{matrix} \]这样的话可以发现答案变小。
于是最终答案一定可以分为若干段,每一段从左边一直跳到右边,然后退回左边,并且两段之间无交。
于是我们考虑以下状态:
\(f(i,j)\) 表示当前在 \(i\),已经从 \(j\) 走到 \(i\) 的答案。
\(g(i,j)\) 表示当前在 \(j\),上一段的最右端是 \(i\) 的答案。
我们每次改变较小的还要改变的位置。
考虑转移有哪些:
第一种是 \(f(i,j) \to g(j,\text{next}(i))\) 即下一段第一个经过的是 \(j\) 。
第二种是 \(g(i,j) \to f(\text{first}(i+1,j),j)\) 即下一段退回到 \(i+1,j\) 这段的第一个必经点。
第三种是 \(g(i,j) \to g(\text{next}(i),j)\) 即上一段延长了。
不难发现 \(j\) 就是 \(i\) 或者 \(i\) 的后继节点,于是状态是 \(nk\) 的,转移 \(O(k)\) ,于是总复杂度 \(O(nk^2)\)。
需要注意特判以下最后一段以及 \(i=j\) 的转移。
代码
#include<bits/stdc++.h>
using namespace std;
const int N = 7e4+5;
const int table[10]={0,1,2,3,-1,4,5,6,7,8};
int n;char s[N];
bool mark[N];
int ch[N],nxt[N][9],Last[N],f[N][9],g[N][9],Mark[N],h[N];
int ans=0x3f3f3f3f;
inline void Doit(int S){
for(int y=0;y<9;y++)if(nxt[S][y]&&f[S][y]!=0x3f3f3f3f){
if(Mark[nxt[S][y]+1]==0){
ans=min(ans,f[S][y]);continue;
}else{
for(int j=0;j<9;j++)if(nxt[nxt[S][y]+1][j]){
int ny = nxt[nxt[S][y]+1][j];
ans=min(ans,f[S][y]+h[ny]+ny-nxt[S][y]);
}
}
for(int y2=0;y2<9;y2++)if(nxt[S+1][y2]){
int to1=nxt[S][y], to2=nxt[S+1][y2];
if(to2>=to1){
int nxt = Mark[to1+1];nxt=min(nxt,to2);
f[nxt][y2]=min(f[nxt][y2],to2-nxt+f[S][y]+2);
}
//if(to2<=to1) f[to2][y]=min(f[to2][y],2+f[S][y]);
}
int to1=nxt[S][y];
if(to1==S){
for(int y2=0;y2<9;y2++)if(nxt[S+1][y2]){
for(int y3=0;y3<9;y3++)if(nxt[S+1][y3]){
int to2=nxt[S+1][y2],to3=nxt[S+1][y3];
if(to2<to3)g[to2][y3]=min(g[to2][y3],f[S][y]+2+2+to2-S);
}
}
}
for(int y2=0;y2<9;y2++)if(nxt[S+1][y2]){
int to2=nxt[S+1][y2];
if(to2>=to1)g[to1][y2]=min(g[to1][y2],f[S][y]+2);
}
}
for(int y=0;y<9;y++)if(nxt[S][y]&&g[S][y]!=0x3f3f3f3f){
for(int y2=0;y2<9;y2++)if(nxt[S+1][y2]){
int to1=nxt[S][y], to2=nxt[S+1][y2];
if(to2<=to1)g[to2][y]=min(2+to2-S+g[S][y],g[to2][y]);
}
int _nxt=min(Mark[S+1],nxt[S][y]);
f[_nxt][y]=min(f[_nxt][y],g[S][y]+nxt[S][y]-_nxt);
}
}
int main()
{
cin >> n;
scanf("%s",s+1);
int _n=0;
int ans2=0;
for(int i=1;i<=n;i++){
if(s[i]=='e'){ans2+=2;continue;}
else ch[++_n]=table[s[i]-'a'],mark[_n]=s[i-1]=='e';
}
mark[1]=1;n=_n;
for(int i=n;i;i--){
Mark[i]=mark[i]?i:Mark[i+1];
Last[ch[i]]=i;
for(int j=0;j<9;j++)nxt[i][j]=Last[j];
}
for(int i=n;i;i--){
h[i]=0x3f3f3f3f;if(!Mark[i+1])h[i]=2;
for(int j=0;j<9;j++)if(nxt[i+1][j]){
int nx=nxt[i+1][j];
if(nx)h[i]=min(h[i],h[nx]+2+nx-i);
}
}
for(int i=1;i<=n;i++)for(int j=0;j<9;j++){g[i][j] = f[i][j] = 0x3f3f3f3f;}
f[1][ch[1]]=0;
for(int i=1;i<=n;i++)Doit(i);
cout << ans + ans2 << endl;
}