多边形游戏
项目要求实现给出得到最高分的删除方案且界面友好。
多边形游戏
游戏简介
多边形游戏是一个单人玩的游戏,开始时有一个由 n 个顶点构成的多边形。每个顶点被赋予一个整数值,每条边被赋予一个运算符 +
或 *
。所有边依次用整数从 1 到 n 编号,游戏第 1 步,将一条边删除。
随后 n-1 步按以下方式操作:
选择一条边 E 以及由 E 连接着的两个顶点 \(V_1\) 和 \(V_2\);
用一个新的顶点取代边E以及由E连接着的两个顶点 \(V_1\) 和 \(V_2\)。将由顶点 \(V_1\) 和 \(V_2\) 的整数值通过边E上的运算得到的结果赋予新顶点。
最后,所有边都被删除,游戏结束。游戏的得分就是所剩顶点上的整数值。
问题:对于给定的多边形,计算最高分。
算法设计
最优子结构性质
设所给的多边形的顶点和边的顺时针序列为
\[ op[1], v[1], op[2], v[2], ..., op[n], v[n] \]
其中,\(op[i]\) 表示第 \(i\) 条边所对应的运算符,\(v[i]\) 表示第 \(i\) 个顶点上的数组,\(i=1\sim n\)。
在所给多边形中,从顶点 \(i(1 \leq i \leq n)\) 开始,长度为 \(j\) (链中有 \(j\) )个顶点的顺时针链 \(p(i+s)\) 可表示为
\[ v[i], op[i+1], ..., v[i+j-1] \]
如果这条链的最后一次合并运算在 \(op[i+s]\) 处发生 \((1 \leq s \leq j-1)\),则可以在 \(op[i+s]\) 处将链分割为两个子链 \(p(i,s)\) 和 \(p(i+s,j-s)\)。
设 \(m_1\) 是对子链 \(p(i,s)\) 的任意一种合并方式得到的值,而 \(a\) 和 \(b\) 分别是在所有可能的合并中得到的最小值和最大值。\(m_2\) 是 \(p(i+s,j-s)\) 的任意一种合并方式得到的值,而 \(c\) 和 \(d\) 分别是在所有可能的合并中得到的最小值和最大值。依此定义有
\[ a \leq m_1 \leq b,\; c \leq m_2 \leq d \]
由于子链 \(p(i,s)\) 和 \(p(i+s,j-s)\) 的合并方式决定了 \(p(i,j)\) 在 \(op[i+s]\) 处断开后的合并方式,在 \(op[i+s]\) 处合并后其值为
\[ m =(m_1)op[i+s](m_2) \]
当 \(op[i+s]='+'\) 时,显然有
\[ a+c \leq m \leq b+d \]
当 \(op[i+s]='*'\) 时,由于 \(v[i]\) 可取负整数,子链的最大值相乘未必能得到主链的最大值。但最大值一定是在边界点达到,即
\[ min \left\{ ac,ad, bc,bd \right\} \leq m \leq max \left\{ ac,ad,bc,bd\right\} \]
换句话说,主链的最大值和最小值可由子链的最大值和最小值得到。例如,当 \(m=ac\) 时,最大主链由它的两条最小链组成;同理当 \(m=bd\) 时,最大主链由它的两条最大子链组成。
递归求解
为了求链合并的最大值,必须同时求子链合并的最大值和最小值。
设 \(m[i,j,0]\) 是链 \(p(i,j)\) 合并的最小值,而 \(m[i,j,1]\) 是最大值。若最优合并在 \(op[i+s]\) 处将 \(p(i,j)\) 分成两个长度小于 \(j\) 的子链 \(p(i,i+s)\) 和 \(p(i+s,j-s)\),且从顶点 \(i\) 开始的长度小于 \(j\) 的子链的最大值和最小值均已计算出。记
\[ a=m[i,i+s,0]\\ b=m[i,i+s,1]\\ c =m[i+s,j-s,0]\\ d=m[i+s,j-s,1] \]
- 当 \(op[i+s]='+'\) 时,
\[ m[i,j,0]=a+c\\ m[i,j,1]=b+d \]
- 当 \(op[i+s]='*'\) 时,
\[ m[i,j,0]=min\left\{ac,ad,bc,bd\right\}\\ m[i,j,1]=min\left\{ac,ad,bc,bd\right\} \]
综合 (1) 和 (2),将 \(p(i,j)\) 在 \(op[i+s]\) 处断开的最大值记为 \(maxf(i,j,s)\),最小值记为 \(minf(i,j,s)\),则
\[ minf(i,j,s)=\left\{\begin{matrix} a+c \qquad \qquad \qquad \qquad \; op[i+s]='+' \\ min\left\{ ac,ad,bc,bd\right\} \qquad op[i+s]='*' \end{matrix}\right. \]
\[ maxf(i,j,s)=\left\{\begin{matrix} b+d \qquad \qquad \qquad \qquad \; op[i+s]='+' \\ min\left\{ ac,ad,bc,bd\right\} \qquad op[i+s]='*' \end{matrix}\right. \]
由于最优断开位置 \(s\) 有 \(1 \leq s \leq j-1\) 的 j-1 种情况,由此可知
\[ m[i,j,0] = \min_{i \leq s < j }\left\{minf(i,j,s)\right\} \qquad 1 \leq i,j \leq n \\ m[i,j,1] = \max_{i \leq s < j }\left\{maxf(i,j,s)\right\} \qquad 1 \leq i,j \leq n \]
初始边界为
\[ m[i,1,0]=v[i] \qquad 1 \leq i \leq n \\ m[i,1,1]=v[i] \qquad 1 \leq i \leq n \]
\(m[i,n,1]\) 即为游戏首次删去第 \(i\) 条边后得到的最大得分。
算法描述
1 | import java.util.HashMap; |
最优合并顺序
在上述算法中,为了能简单地得到一个最优的合并顺序,使用了一个 cut[][][]
数组来记录断点位置。
其中只在 m[i][j][0]
或 m[i][j][1]
进行更新时,相应地也进行更新,保证 cut[i][j][0]
为 m[i][j][0]
的最优断点,cut[i][j][1]
为 m[i][j][1]
的最优断点。
这里需要说明的是,cut[i][j][]
中保存的 s,指的是距离顶点 i 的距离,若 s=1,即从说明从 i 顶点开始(包括 i 顶点)只包含一个顶点,也就说要从 i 顶点连着的顺时针的边断开。
其实,计算得到最优分数的过程是一个自底向上的过程,而寻找最优合并顺序则相反,是一个自顶向下的过程。
基本的思路就是,使用一个栈来保存合并边的编号
- (1)从最后的主链开始,找到最优的合并边,入栈
- (2)判断合并边的符号,如果是
+
,转 (3);如果是*
,转 (4) - (3)如果为
+
,判断主链需要最大还是最小- 如需最大,则递归取两条子链的最大;
- 否则,递归取两条子链的最小
- (4)如果为
*
,判断主链需要最大还是最小- 如需最大,则在
{ac, ad, bc, bd}
取最大的情况,进行相应递归调用(如 ac,则递归时,两条子链都需要取最小值) - 否则,则在
{ac, ad, bc, bd}
取最小的情况,进行相应递归调用
- 如需最大,则在
复杂度分析
寻找最优合并顺序
递归深度最好的情况是 \(logn\),也就说每次都恰好是对半进行合并;最坏情况是 n-1,每次都合并单个顶点;
一次递归的过程中,计算时间为常数级别C,所以整个时间复杂度为递归调用次数,即 \(O(C(n-1)) = O(n)\)
总的时间复杂度
动规过程需要 \(O(n^3)\) 计算时间,加寻找最优合并顺序的时间 \(O(n)\),总的时间复杂度为 \(O(n^3)\)
测试
这里说明一下 output,前面的 i=x xxx
表示一开始删除那条边所能得到的最高分,接着 firstDelEdge=x
表示一开始删除这条边最终能得到最高分,后面的 stack--> x
是紧接着要按顺序删除的边,最后是该删除方案的得分。
以 Case 1 为例,从左往右对边进行编号,第一条边为 *
,连接第一个数字 -5
和最后一个数字 8
;类似地,第二条边为 +
,连接第一个数字 -5
和第二个数字 -2
,其它同理。
Case 1
Input
1
25
* -5 + -2 * -8 * -5 + 8Output
1
2
3
4
5
6
7
8
9
10
11
12
13|-5|--+--|-2|--*--|-8|--*--|-5|--+--|8|
|_________________*_________________|
i=1 168
i=2 480
i=3 488
i=4 488
i=5 120
firstDelEdge=3
stack--> 2
stack--> 1
stack--> 5
stack--> 4
BestScore=488Case 2
Input
1
25
* -6 + -7 * 0 * 4 + -2Ouput
1
2
3
4
5
6
7
8
9
10
11
12
13|-6|--+--|-7|--*--|0|--*--|4|--+--|-2|
|________________*_________________|
i=1 26
i=2 12
i=3 26
i=4 16
i=5 48
firstDelEdge=5
stack--> 3
stack--> 2
stack--> 4
stack--> 1
BestScore=48Case 3
Input
1
26
+ 5 * 3 + -2 + 1 * -10 * -2Ouput
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15|5|--*--|3|--+--|-2|--+--|1|--*--|-10|--*--|-2|
|_____________________+_____________________|
i=1 280
i=2 50
i=3 130
i=4 73
i=5 74
i=6 63
firstDelEdge=1
stack--> 6
stack--> 4
stack--> 2
stack--> 3
stack--> 5
BestScore=280Case 4
Input
1
28
+ -2 + 9 * -5 + -4 * -5 * 0 + 7 * -5Ouput
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19|-2|--+--|9|--*--|-5|--+--|-4|--*--|-5|--*--|0|--+--|7|--*--|-5|
|_____________________________+______________________________|
i=1 2905
i=2 3969
i=3 630
i=4 5080
i=5 3080
i=6 3080
i=7 200
i=8 3080
firstDelEdge=4
stack--> 1
stack--> 8
stack--> 7
stack--> 6
stack--> 2
stack--> 3
stack--> 5
BestScore=5080Case 5
Input
1
29
* 0 * -10 + 8 * -4 + 3 * -7 + 7 * -3 * -4Ouput
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21|0|--*--|-10|--+--|8|--*--|-4|--+--|3|--*--|-7|--+--|7|--*--|-3|--*--|-4|
|__________________________________*__________________________________|
i=1 2816
i=2 273
i=3 2000
i=4 2816
i=5 5376
i=6 899
i=7 19257
i=8 2758
i=9 2816
firstDelEdge=7
stack--> 4
stack--> 2
stack--> 3
stack--> 1
stack--> 5
stack--> 6
stack--> 9
stack--> 8
BestScore=19257Case 6
Input
1
24
+ -7 + 4 * 2 * 5Ouput
1
2
3
4
5
6
7
8
9
10
11|-7|--+--|4|--*--|2|--*--|5|
|___________+____________|
i=1 33
i=2 33
i=3 7
i=4 6
firstDelEdge=1
stack--> 4
stack--> 3
stack--> 2
BestScore=33
项目演示和源码
References
王晓东《算法设计与分析》第三版