CF1835E. Old Mobile
事先声明,本题解完全参考自这篇题解,主要是对原题解的若干处笔误以及一些阅读起来可能会产生困惑的地方进行补充说明。
题目链接:E - Old Mobile
题目大意:给定一个 \(m+1\) 个按键的手机,其中一个是删除,\(m\) 个是 \(1\) 到 \(m\) 的数字,但是你不知道每个按键对应的是啥(不过按完之后就知道了)。给你一个长度为 \(n\) 的数字串,询问你打出这个串的期望按键次数。\(n\le 10^6,m\le 10^3\)
首先策略是明显的:
- 当还未确定删除键在哪时:
- 如果屏幕上显示的数字串完全正确(即,是目标串的一个前缀),并且知道哪个键是接下来要输入的字符,则输入该字符
- 否则我们需要删去多余字符,直接在未知按键里乱按直到按到删除键为止,就能清空多余字符
- 确定了删除键之后,当前串一定是结果串的前缀,此时:
- 若确定下一个字符的按键,直接输入即可
- 否则还是乱按,按错了就删除,直到找到对应按键
如此这般直至输入完成即可。
不难发现只有在某一个字符在目标串中第一次出现的位置的时候,我们会遇到不知道按哪个键的情况。也就是说,将字符按照其第一次出现的位置排序,那么我们只会在极长的包含前 \(k\) 个字符的地方卡住。这里我们就直接认为第 \(i\) 次出现的新字符就是字符 \(i\)。
于是把这几个可能卡住的地方求出来,设 \(l_i\) 表示包含前 \(i\) 个字符的极长前缀长度,我们只需要在这些位置之间转移。
具体的,设 \(f_{i,j,1/2/3/4}\) 表示已经完成了 \([1,l_i]\) 的输入,目前有 \(j\) 个已确定键位但尚未输入的字符,第三维分别表示:
- 不知道删除键位置,当前串是结果串的前缀
- 不知道删除键位置,当前串不是结果串的前缀
- 知道删除键位置,不确定知不知道下一个字符的键位
- 知道删除键位置,确定不知道下一个字符的键位
时,期望还需多少步才能完成输入。
注意:这里所谓是否确定知道下一个字符键位,实际上表述的是,当我们按错一个键时,只能确定它不是当前字符。那么我们先不去确定具体按到的是哪一个键,这些可能性到需要填新字符的时候再去分配填错的是下一个字符还是其他字符。
现在开始对四种情况分别讨论转移式
- 不知道删除键位置,当前串是结果串的前缀
- 此时策略一定是在不知道是啥的里面瞎按一个,因为没有删除键,所以之前一定没有输错过,也就不知道任何除已经输入的字符之外的字符。(也就是说,第三维是 \(1\) 时有效状态只有 \(f_{i,0,1}\))
- 在剩下的 \(m-i+1\) 个按键中:
- \(1\) 种情况继续狗运,那么可以接着输入 \(l_{i+1}-l_i\) 个字符,到达状态 \(f_{i+1,0,1}\)
- \(1\) 种情况按到删除键,这里如果不是开头需要重新输一次字符,所以花费是 \(1+[i\neq 0]\),到达状态 \(f_{i,0,3}\)
- 剩下 \(m-i-1\) 种情况都是按错,这里提前计算删除他的代价,所以花费是 \(2\),到达状态 \(f_{i,1,2}\)
- 不知道删除键位置,当前串不是结果串的前缀
- 此时策略只能是接着瞎按,期待着碰到删除键
- 在剩下的 \(m-i-j+1\) 个按键中:
- \(m-i-j\) 种情况接着按错,同样花费为 \(2\),到达状态 \(f_{i,j+1,2}\)
- \(1\) 种情况成功按到删除键,由于之前删除错误字符的花费已经计算,所以直接到达状态 \(3/4\)
- 这里需要确定之前输错字符的情况了,除了第一次输错的字符一定不是 \(i+1\) 以外,其余 \(j-1\) 个字符都可以是 \(i+1\),对应概率就是 \(\frac{j-1}{m-i-1}\)
- 而当确定 \(i+1\) 在 \(j\) 个字符内时就可以输入了,所以综合起来是 \(\frac{1}{m-i-j+1}\cdot \frac{j-1}{m-i-1}\) 概率花费 \(l_{i+1}-l_i\) 到达状态 \(f_{i+1,j-1,3}\);\(\frac{1}{m-i-j+1}\cdot \frac{m-i-j}{m-i-1}\) 概率花费 \(0\) 到达 \(f_{i,j,4}\)
- 知道删除键位置,不确定知不知道下一个字符的键位
- 此时需要分配志愿,和前面的分类讨论一样,有 \(\frac{j}{m-i}\) 概率是能找到 \(i+1\) 的,花费 \(l_{i+1}-l_i\) 到达状态 \(f_{i+1,j-1,3}\);剩下就是 \(\frac{m-i-j}{m-i}\) 的概率到达 \(f_{i,j,4}\)
- 知道删除键位置,确定不知道下一个字符的键位
- 此时直接在剩下的 \(m-i-j\) 个键里按即可,在这些按键中:
- 有 \(1\) 种可能按对,花费 \(l_{i+1}-l_i\) 到达 \(f_{i+1,j,3}\)
- 剩下 \(m-i-j-1\) 种可能按错,花费 \(2\) 到达 \(f_{i,j+1,4}\)
至此我们完成了所有的分类讨论,直接转移即可,最后答案就是 \(f_{0,0,1}\),时间复杂度 \(O(m^2)\),需要注意转移时的边界判断以及 \(1,2,3,4\) 之间的转移顺序(取任意合法拓扑序均可)。
#include<bits/stdc++.h>
using namespace std;
#define N 1010
#define LL long long
#define MOD 1000000007
int n,M,m,x,v[N],l[N],inv[N],f[N][N][5];
int main()
{
inv[1]=1;
for(int i=2;i<N;i++)inv[i]=1ll*(MOD-MOD/i)*inv[MOD%i]%MOD;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%d",&x);
if(!v[x])l[M++]=i-1;
v[x]=1;
}
l[M]=n;
for(int i=M-1;i>=0;i--)
for(int j=m-i;j>=0;j--){
f[i][j][4]=(1ll*inv[m-i-j]*(f[i+1][j][3]+l[i+1]-l[i])+f[i][j][4])%MOD;
f[i][j][4]=(1ll*(m-i-j-1)*inv[m-i-j]%MOD*(f[i][j+1][4]+2)+f[i][j][4])%MOD;
if(j)f[i][j][3]=(1ll*j*inv[m-i]%MOD*(f[i+1][j-1][3]+l[i+1]-l[i])+f[i][j][3])%MOD;
f[i][j][3]=(1ll*(m-i-j)*inv[m-i]%MOD*f[i][j][4]+f[i][j][3])%MOD;//4->3
if(j==0){
f[i][0][1]=(1ll*inv[m-i+1]*(f[i+1][0][1]+l[i+1]-l[i])+f[i][0][1])%MOD;
f[i][0][1]=(1ll*inv[m-i+1]*(f[i][0][3]+(i?2:1))+f[i][0][1])%MOD;//3->1
f[i][0][1]=(1ll*(m-i-1)*inv[m-i+1]%MOD*(f[i][1][2]+2)+f[i][0][1])%MOD;
}
f[i][j][2]=(1ll*(m-i-j)*inv[m-i-j+1]%MOD*(f[i][j+1][2]+2)+f[i][j][2])%MOD;
if(j)f[i][j][2]=(1ll*inv[m-i-j+1]*(j-1)%MOD*inv[m-i-1]%MOD*(f[i+1][j-1][3]+l[i+1]-l[i])+f[i][j][2])%MOD;
f[i][j][2]=(1ll*inv[m-i-j+1]*(m-i-j)%MOD*inv[m-i-1]%MOD*f[i][j][4]+f[i][j][2])%MOD;//4->2
}
printf("%d\n",f[0][0][1]);
}

浙公网安备 33010602011771号