题目链接
题意
光标只能使用“跳到下一个字符 $\alpha$”和“跳到上一个字符 $\alpha$”这两种命令来移动,求一个字符串中任意两个位置间移动的最短命令长度和。
题解
首先把操作反序,发现一次反操作就是把光标移动回在上一个和下一个本字符之间的任一位置。
假设 $i$ 的反操作可达区间为 $[L_i, R_i]\setminus\{i\}$, 再记 $i$ 经过至多 $k$ 次反操作可达区间为 $[L_i^{(k)}, R_i^{(k)}]$.
那么 $[L_i^{(k+1)}, R_i^{(k+1)}]=\bigcup_{L_i^{(k)} \le x \le R_i^{(k)}}[L_x, R_x]$.
考虑在 $k$ 步之内还有哪些点不能到达,于是答案被改写成 $\sum_{i=0}^{n-1}\sum_{k=0}^{n-1}(n-1-L_i^{(k)}+R_i^{(k)})$.
由于 $L^{(k)}$ 和 $R^{(k)}$ 互相影响,直接对着它们优化已经比较困难了。
这里的思路比较巧妙。我们考虑一下 $[L_i^{(k)}, R_i^{(k)}]=[l, r]$ 进行一轮迭代后的区间 $[L_i^{(k+1)}, R_i^{(k+1)}]=[l', r']$。我们发现:如果假设 $p_1, p_2, \ldots, p_t$ 是该区间内所有字符的最左出现位置,那么得出 $l'=\min_{1 \le j \le t}L_{p_j}$. 进一步地,我们假设 $[l, r]$ 内一共有 $t$ 种字符,而 $l$ 以后字符的最左出现位置从左到右依次为 $p_1<p_2<\cdots<p_{26}$(如果不存在设为 $n-1$),那么仍然有 $l'=\min_{1 \le j \le t}L_{p_j}$. 因此我们可以设 $f_t(i)=\min_{1 \le j \le t}L_{p_j}$, $g_t(i)$ 对应右端点的类似量。当 $t$ 不变时,$L_i^{(k)}$ 和 $R_i^{(k)}$ 独立。因此我们枚举 $t$, 对于每个 $i$ 求出最大的区间 $[L_i^{(k)}, R_i^{(k)}]$ 使得其中字符种数为 $t$. 用倍增求出 $f_t, g_t$ 各自迭代 $2^x$ 轮的结果以及迭代过程中的函数值之和,每次以 $t$ 不变为迭代限度进行倍增迭代即可求出答案。
具体实现上,判断区间内种类数是否为 $t$ 可以借助前述的位置序列 $\{p_j\}$.
实现
#include <bits/stdc++.h> #define meow(args...) fprintf(stderr, args) template<class T1, class T2> inline bool cmin(T1 &a, const T2 &b) {return b<a?(a=b, true):false;} template<class T1, class T2> inline bool cmax(T1 &a, const T2 &b) {return a<b?(a=b, true):false;} const int N=1e5+5; int n, last[26], l[N], r[N], pre[N][27], nex[N][27], f[N][27], g[N][27], curl[N], curr[N], flift[17][N], glift[17][N]; char s[N]; long long fsum[17][N], gsum[17][N]; int main() { assert(scanf("%s", s)==1); n=strlen(s); for(int i=0; i<n; ++i) s[i]-='a'; for(int i=0; i<n; ++i) { l[i]=last[s[i]]; last[s[i]]=i; memcpy(pre[i], last, sizeof(last)); std::sort(pre[i], pre[i]+26, std::greater<int>()); pre[i][26]=-1; } for(int i=0; i<26; ++i) last[i]=n-1; for(int i=n; i--; ) { r[i]=last[s[i]]; last[s[i]]=i; memcpy(nex[i], last, sizeof(last)); std::sort(nex[i], nex[i]+26); nex[i][26]=n; } for(int i=0; i<n; ++i) { curl[i]=curr[i]=i; f[i][0]=g[i][0]=i; for(int j=0; j<26; ++j) { f[i][j+1]=std::min(f[i][j], l[nex[i][j]]); g[i][j+1]=std::max(g[i][j], r[pre[i][j]]); } } long long ans=0; for(int t=1; t<=26; ++t) { for(int i=0; i<n; ++i) { flift[0][i]=f[i][t]; glift[0][i]=g[i][t]; fsum[0][i]=gsum[0][i]=i; } for(int i=1; (1<<i)<=n; ++i) for(int j=0; j<n; ++j) { flift[i][j]=flift[i-1][flift[i-1][j]]; glift[i][j]=glift[i-1][glift[i-1][j]]; fsum[i][j]=fsum[i-1][j]+fsum[i-1][flift[i-1][j]]; gsum[i][j]=gsum[i-1][j]+gsum[i-1][glift[i-1][j]]; } for(int i=0; i<n; ++i) { for(int j=31-__builtin_clz(n); j>=0; --j) { int nexl=flift[j][curl[i]], nexr=glift[j][curr[i]]; if(nexr<nex[nexl][t]) { ans+=((n-1ll)<<j)+fsum[j][curl[i]]-gsum[j][curr[i]]; curl[i]=nexl, curr[i]=nexr; } } if(curr[i]<nex[curl[i]][t]) { ans+=n-1+curl[i]-curr[i]; curl[i]=f[curl[i]][t]; curr[i]=g[curr[i]][t]; } } } printf("%lld\n", ans); return 0; }