实验目的和要求
目的:利用动态规划实现多边形游戏
要求:给定一个多边形,顶点和边已进行标注。问:按照游戏规则,最高得分(最优值)是多少?对应最高得分,按照什么顺序移走边(最优解)?
规则:游戏第一步,将一条边删除。随后n-1步按照以下方式操作
- 选择一条边E以及由E连接着的两个顶点V1和V2;
- 用一个新顶点取代边E以及由E连接着的两个顶点V1和V2。将由顶点V1 和V2的整数值通过边E上的运算得到的结果赋予新顶点。
解题思路
①由于多边形为环形,为方便处理,创建一个两倍于多边形顶点数的数组,用于存储多边形顶点。即val[i+n] = val[i],i<n。
②根据游戏规则,边的删除是不按照顺序进行的,即若需要求i号顶点到j号顶点的最高得分,则i与j之间必然存在一点k,使最高得分为i到k的最优值与k+1到j的最优值进行k号运算符的运算。转化为动态规划,状态转移方程:dp[i][j]=max{dp[i][j],count(dp[i][k],dp[k+1][j],sign[k])},count(int a,int b,char c)用于计算a与b按照c进行运算的最大值。
③动态规划初值问题,dp[i][i]表示i号顶点到i号顶点的最优值,即为val[i]。n个顶点的最优值可以通过一次求解1个,2个……n个顶点的情况来求解。
④关于最大值得求解问题,由于涉及到了加号与乘号的问题。
对于加号来说,求两段顶点序列的计算最大值即为两侧最大值之和。
对于乘号来说,由于考虑到最大值也有可能是负数乘以负数得到,但两段顶点序列的最大值必然在边缘取到。即max{最大*最小,最小*最小,最大*最大,最小*最大}所以需要另外维护一个数组用于存储每一步计算的最小值。最小值的求解类似于最大值。
⑤关于输出最优解的问题,由于使用动态规划要求无后向性与最优化原理,且该问题设计两个dp数组,无法在计算dp数组的同时保存最优解。因此考虑在计算dp完成后反向递归推导最优解。由于dp数组的每一行相当于对最开始删除某一条边的情况,则求出最优值后,对这一行进行遍历,当count(dp[i][k],dp[k+1][j],sign[k])等于最大值时,将sign[k]存入临时数组(为便于识别可以将k也存入另一临时数组),然后对i与k之间,k+1与j之间进行同样的操作,对应的最大值即为dp[i][k],dp[k+1][j]。递归运算完成后对数组倒序输出即可。
代码
#include<stdio.h>
#define maxnum 100
#define Max 999999
#define Min -999999
int dp[maxnum*2][maxnum*2][2]; //dp[][][0]用于存储最大值,dp[][][1]用于存储最小值
int val[maxnum]; //存储顶点值
char sign[maxnum]; //存储边上符号
char order[maxnum]; //存储各种情况下的最优删除顺序
int ordord=0;
int ord[maxnum];
int countMax(int i,int k,int len,char c){ //用于计算最大值
int max = Min,temp;
if(c=='+'){
return dp[i][k][0]+dp[k+1][i+len][0];
}
if(c=='*'){
temp = dp[i][k][0]*dp[k+1][i+len][0];
if(temp>max){
max = temp;
}
temp = dp[i][k][0]*dp[k+1][i+len][1];
if(temp>max){
max = temp;
}
temp = dp[i][k][1]*dp[k+1][i+len][1];
if(temp>max){
max = temp;
}
temp = dp[i][k][1]*dp[k+1][i+len][0];
if(temp>max){
max = temp;
}
return max;
}
}
int countMin(int i,int k,int len,char c){ //用于计算最小值
int min = Max,temp;
if(c=='+'){
return dp[i][k][1]+dp[k+1][i+len][1];
}
if(c=='*'){
temp = dp[i][k][1]*dp[k+1][i+len][1];
if(temp<min){
min = temp;
}
temp = dp[i][k][0]*dp[k+1][i+len][0];
if(temp<min){
min = temp;
}
temp = dp[i][k][1]*dp[k+1][i+len][0];
if(temp<min){
min = temp;
}
temp = dp[i][k][0]*dp[k+1][i+len][1];
if(temp<min){
min = temp;
}
return min;
}
}
void getord(int maxline,int maxn,int n){
int flag=0;
int k;
int len = n-1;
if(maxline==maxline+n-1){
return;
}
else{
for(k=maxline;k<maxline+len;k++){
if(countMax(maxline,k,len,sign[k])==maxn){
order[ordord] = sign[k];
ord[ordord] = k;
ordord++;
break;
}
}
getord(maxline,dp[maxline][k][0],k-maxline+1);
getord(k+1,dp[k+1][maxline+n-1][0],maxline+n-1-k-1+1);
}
}
int main(){
int n;
scanf("%d",&n);
for(int i=0;i<n;i++){
scanf("%d",&val[i]);
scanf("%c",&sign[i]);
val[i+n] = val[i];
sign[i+n] = sign[i];
}
for(int i=0;i<2*n;i++){
dp[i][i][0] = val[i];
dp[i][i][1] = val[i];
}
for(int len=1;len<n;len++){
for(int i=0;i<n;i++){
int max=Min,min=Max,tempmax,tempmin;
for(int k=i;k<i+len;k++){
tempmax = countMax(i,k,len,sign[k]);
tempmin = countMin(i,k,len,sign[k]);
if(tempmax>max){
max = tempmax;
}
if(tempmin<min){
min = tempmin;
}
}
dp[i][i+len][0] = max;
dp[i][i+len][1] = min;
if(i+len+n<2*n){
dp[i+n][i+n+len][0] = dp[i][i+len][0];
dp[i+n][i+n+len][1] = dp[i][i+len][1];
}
}
}
int maxline,maxn = Min;
for(int i=0;i<n;i++){
if(dp[i][i+n-1][0]>maxn){
maxn = dp[i][i+n-1][0];
maxline = i;
}
}
if(maxline==0){
printf("最优值为%d,删除%d号边得到\n",maxn,maxline+n);
}
else
printf("最优值为%d,删除%d号边得到\n",maxn,maxline);
getord(maxline,maxn,n);
printf("删除边的顺序为:");
for(int i=n-2;i>=0;i--){
printf("%c(%d)",order[i],ord[i]%n+1);
}
}