首页 > 技术文章 > Loj #2687 - 「BalticOI 2013」Vim

weiyanpeng 2020-01-14 23:41 原文

搞了一个 \(nk^2\) 的做法。

首先如果前面是一个 \(e\) 那下一步一定是将 \(e\) 删掉,花费 \(2\)

那么可以将 \(e\) 删掉,每个之前有 \(e\) 的点都必须经过,求最短的方案。

答案一定是向后走一段,然后把前面的所有必经点都走掉,于是可以轻松得到 \(O(n^2)\) 的做法。

现在考虑优化。

很容易想到的是每次只向后走一个,然后立即将前面所有必经点走掉。如果这个结论正确那状态是 \(nk\) 的,很容易转移。

然而需要注意这里向后走的费用是 \(2\),于是类似:

\[\begin{aligned} &ca \dots aaba\\ &01 \dots 1011 \end{aligned} \]

这样的数据可以卡掉。

但是我们可以考虑一个更平凡的结论:

  • 考虑每次向后一段再向前,再向后一次,最终到达的那一个节点一定是第一次到达。

证明:例如:

\[\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;
}

推荐阅读