代码: 最速输入问题

代码: 最速输入问题

屏幕上有1个初始字符, 只使用Ctrl+A, Ctrl+C, Ctrl+V, n个字符最少需要多少次按键?

其实就是整数分解

\mathtt{dp[i] =Min[dp[i], dp[j] + i/j + 2, dp[i/j] + j + 2];dp[1]=0}

教一个小技巧, 匿函递归

If[#==1,0,With[{l=Rest@Reverse@Divisors@#},Min[#0/@l+#/l+2]]]&[10000]

一个函数式语言都不能递归匿名函数那可能是对面混进来的奸细......

如果要输出这个序列稍微麻烦点

stdp[1]=ConstantArray["V",dp[1]=0];
dp[i_]:=dp[i]=With[{l=Rest@Reverse@Divisors[i]},Min[dp/@l+i/l+2]];
stdp[i_]:=stdp[i]=Block[
	{l=Rest@Reverse@Divisors[i],st},
	st=Take[l,Ordering[dp/@l+i/l+2,1]];
	Join[stdp@@st,{"A","C"},ConstantArray["V",i/First@st]]
]
TableForm[StringJoin@@@stdp/@#,TableHeadings->{#,None}]&[DeleteCases[Range[30],_?PrimeQ]]
ListLinePlot[dp/@Range[100]]

算法复杂度是PrimeOmega函数, 和因子数量有关.

什么时候用粘贴键比较谜


如果没有初始字符, 但是允许输入

这就比较有趣了

stdp[1]=ConstantArray["E",dp[1]=1];
dp[i_]:=dp[i]=With[
	{l=Rest@Reverse@Divisors[i]},
	Min@Append[dp/@l+i/l+2,dp[i-1]+1]
];
stdp[i_]:=stdp[i]=Block[
	{l=Rest@Reverse@Divisors[i],st},
	st=Ordering[Join[dp/@l+i/l+2,{dp[i-1]+1}],1];
	If[First@st>Length[l],
		Append[stdp[i-1],"E"],
		Join[stdp@@Take[l,st],{"A","C"},ConstantArray["V",i/First@Take[l,st]]]
	]
]
TableForm[StringJoin@@@Array[stdp,20],TableHeadings->{Range[20],None}]
\[Alpha]=a/.FindFit[data=Array[dp,1000],a Log[x],a,x]
Show[ListLinePlot@data,Plot[a Log[x]/. {a->\[Alpha]},{x,1,1000},PlotStyle->Red]]

但是因为要比较 \mathtt{dp[i]}\mathtt{dp[i-1]} ,复杂度一下子上去了.

按键次数符合对数曲线 p(n)=4.35639\log n , 数学原理不明...

这个P值检验有毒...

再加入删除就比较难了,因为不满足最优子结构了

我们索性把难度推到顶


我们来把问题推广到最广义的情况

初始字符数 \mathtt{a_0}
输入代价 E
删除代价 D
全选代价 S
粘贴代价 V
注意第一次粘贴只会把当前内容覆盖掉.

@Lightwing 觉得可能要用抽象代数....好吧, 其实图论就够了...

翻n倍,全选+粘贴, 代价 \mathtt{S+n V}

输入当然是从i-1输入, 代价 \mathtt{E}

删除当然是从i+1删除, 代价 \mathtt{D}

于是我们可以写出代价函数:

cost[i_]:=Block[
	{P=1,D=1,S=2,V=1,l},
	l=Drop[Divisors[i],-1];
	Join[
		Transpose[{Thread[l->i],S+i/l V}],
		{{i-1->i,P},{i+1->i,D}}
	]
]

于是以数字为节点, 转移关系为边, 代价为权, 规模小的时候(n<1000)我们可以使用图编程

\mathtt{Array[cost,100] } 遍历整个求解域, 然后用 \mathtt{Graph} 转化为图对象

但是Mathematica的图编程只能用在单图.

所以还要洗一下数据.

raw=Drop[Flatten[Table[cost[i],{i,2,n+10}],1],-1];
{dir,wei}=Transpose[(SortBy[#,Last]&/@GatherBy[raw,First])[[All,1]]];
G=Graph[dir,EdgeWeight->wei,
	PlotTheme->"Monochrome",
	GraphLayout->"CircularEmbedding"
]

规模大...手动 \mathtt{Indexed} 节点,然后 \mathtt{ BreadthFirstScan} 遍历吧...

左半面毫无疑问是分形结构...但是和整数分解相关...没啥好研究的

问题转变为求节点 a_0 到节点 a_n 的最短路

Dijkstra lalalaa ~~~ so easy...

MatrixForm[
	FindShortestPath[G,1,All,Method->"Dijkstra"]/@Range[20],
	TableHeadings->{Range@20,None},
	TableAlignments->Left
]
MatrixForm[
	GraphDistanceMatrix[G][[1;;20,1;;20]]//Round,
	TableHeadings->{Range@20,Range@20}
]

内置的 \mathtt{GraphDistanceMatrix} 只能用于单图, 如果你装了IGraphM扩展包的话 \mathtt{IGraphM}\text{`}\mathtt{IGDistanceMatrix} 可以用于多重图.

最左边一列1没单独处理,所以出错了,其实就是 j - i

输入和删除代价调到无穷大...等会儿, 无穷大会出错...调到10000就行, 退化到第一种

单单 删除代价调高会慢慢退化到第二种情况.

虽然看上去没有变化但实际上下降到了 p(n)=4.1414 \log n , 数学原理同样不明

代价仍然不是单增的...这个评论就很想当然了......

编辑于 2018-01-10

文章被以下专栏收录