动态规划--多边形游戏

隐身守侯 提交于 2020-01-26 18:01:26

1)问题描述

多边形游戏是一个单人玩的游戏,开始时有一个由n个顶点构成的多边形。每个顶点被赋予一个整数值,每条边被赋予一个运算符+或*。所有边依次用整数从1到n编号,游戏第1步,将一条边删除。
随后n-1步按以下方式操作:
(1)选择一条边E以及由E连接着的两个顶点V1和V2;
(2)用一个新的顶点取代边E以及由E连接着的两个顶点V1和V2。将由顶点V1和V2 的整数值通过边E上的运算得到的结果赋予新顶点。
最后,所有边都被删除(断开),游戏结束。游戏的得分就是所剩顶点上的整数值。问题:对于给定的多边形,计算最高分。

2)代码实现

package hello;
import java.util.HashMap;
import java.util.Scanner;
import java.util.Stack;

public class fuck {
	
    private int n; //多边形边数
    private char[] op; //每条边的对应的操作(从1开始计数)
    private int[] v; //每个顶点数值(从1开始计数)
    private long[][][] m; //m[i][n][1]:代表一开始删除第i条边,长度为n的链(包含n个顶点),所能得到的最大值
                          //m[i][n][0]:代表一开始删除第i条边,长度为n的链,所能得到的最小值
    private int[][][] cut; //cut[i][j][0];表示m[i][j][0]这条链的达到最小数值的时候断开的位置
                           //cut[i][j][1]: 表示m[i][j][1]这条链的达到最大数值的时候断开的位置
    private Stack<Integer> stack; //用栈保存合并边的顺序
    private int firstDelEdge; //记录最优情况下,第1条删除的边
    private long bestScore; //记录最优得分

    //初始化
    public fuck(int n, long[][][] m, char[] op, int[] v){     
        this.n = n;
        this.m = m;
        this.op = op;
        this.v = v;
        this.cut = new int[n+1][n+1][2];
        this.stack = new Stack<>();
    }
    //************************************************************************************************************************************
    /**
     * 子函数
     * 服务于主执行函数,就是主函数的一些方法的封装
     */
    
    

    //把断开的边是*还是+的时候计算最大值和最小值封装在一起,向外暴露一个统一的方法
    private HashMap<String, Long> minMax(int i, int s, int j, HashMap<String, Long> resMap){    
        int r = (i+s-1) % n + 1;
        long a = m[i][s][0], b = m[i][s][1], c = m[r][j-s][0], d = m[r][j-s][1];
        if(op[r] == '+'){
            resMap.put("minf", a+c);
            resMap.put("maxf", b+d);
        }else{
            long[] e = new long[]{0, a*c, a*d, b*c, b*d};
            long minf = e[1], maxf = e[1];
            for (int k = 2; k < 5; k++){
                if(minf > e[k]) minf = e[k];
                if(maxf < e[k]) maxf = e[k];
            }
            resMap.put("minf", minf);
            resMap.put("maxf", maxf);
        }
        return resMap;
    }
    
    /**
     * 获取最优的合并序列,存入stack中
     * @param i 表示子链从哪个顶点开始
     * @param j 子链的长度(如j=2,表示链中有两个顶点)
     * @param needMax 是否取链的最大值,如果传入值为false,则取子链的最小值
     */
    
    //算出子链m[i][j][1(0)]取到最大(小)值的时候要断开的点,并压栈,在需要的时候出栈。
    //递归压栈到子链的长度为1(即j为1的时候)。
    private void getBestSolution(int i, int j, boolean needMax){
    	//needMax为true的话就是把要取子链的最大值的时候断开的点压栈,为false就是把要取子链的最小值的时候断开的点压栈
    	
        int s,r;
        if(j == 1){  //链中只有一个顶点,直接返回      	
        	
        }else if(j == 2){
            s = cut[i][j][1];
            r = (i+s-1) % n + 1;    //因为压栈的r是相对于最开始的,而s是相对于i的。
            stack.push(r);
            
        }else {      //链中有两个以上的顶点时,将最优的边入栈       	
            s = needMax ? cut[i][j][1] : cut[i][j][0];      
            r = (i+s-1) % n + 1;
            stack.push(r);
            
            //当j>2的时候的递归操作
            if(this.op[r] == '+'){                                   //当合并计算为"+"操作时
                if(needMax){ //如果合并得到的父链需要取得最大值
                    getBestSolution(i, s, true);
                    getBestSolution(r, j-s, true);
                }else { //如果合并得到的父链需要取得最小值
                    getBestSolution(i, s, false);
                    getBestSolution(r, j-s, false);
                }
            }else{                                                      //当合并计算为"*"操作时
                long a = m[i][s][0], b = m[i][s][1], c = m[r][j-s][0], d = m[r][j-s][1];
                long[] e = new long[]{0, a*c, a*d, b*c, b*d};
                long mergeMax = e[1], mergeMin = e[1];
                for(int k=2; k<=4; k++){
                    if(e[k] > mergeMax) mergeMax = e[k];
                    if(e[k] < mergeMin) mergeMin = e[k];
                }
                long merge = ((needMax) ? mergeMax : mergeMin);   //判断合并得到的父链是取最大还是取最小
                if(merge == e[1]){ //子链1和子链2都取最小
                    getBestSolution(i, s, false);
                    getBestSolution(r, j-s, false);
                }else if(merge == e[2]){ //子链1取最小,子链2取最大
                    getBestSolution(i, s, false);
                    getBestSolution(r, j-s, true);
                }else if(merge == e[3]){ //子链1取最大,子链2取最小
                    getBestSolution(i, s, true);
                    getBestSolution(r, j-s, false);
                }else { //子链1和子链2都取最大
                    getBestSolution(i, s, true);
                    getBestSolution(r, j-s, true);
                }
            }
        }
        
    }
    
    //************************************************************************************************************************************   
    /**
     * 主执行函数
     * 就是主要的算法的核心
     */
    
    

    //算出断开哪个的点的时候最值并把最值填入m[i][j][0] 和 m[i][j][1];把断开的点填入cut[i][j][0]和cut[i][j][1]
    private void polyMax(){
    	
    	//1 填表 m[i][j][0] 和 m[i][j][1] 和 cut[i][j][0] 和 cut[i][j][1]
        HashMap<String, Long> resMap = new HashMap<>();
        for (int j = 2; j <= n; j++){            //链的长度
            for(int i = 1; i<= n; i++){          //一开始断开第i条边的时候
                m[i][j][0] = Long.MAX_VALUE;
                m[i][j][1] = Long.MIN_VALUE;
                for(int s = 1; s < j; s++){      //断开的位置
                    resMap = this.minMax(i, s, j, resMap);
                    if(m[i][j][0] > resMap.get("minf")){
                        m[i][j][0] = resMap.get("minf");
                        cut[i][j][0] = s; //记录该链取得最小值的断点
                    }
                    if(m[i][j][1] < resMap.get("maxf")){
                        m[i][j][1] = resMap.get("maxf");
                        cut[i][j][1] = s; //记录该链取得最大值的断点
                    }
                }
            }
        }
        
        //2 根据表m算出第一次断开哪里的时候的数值最大,并输出一些相关数据
        bestScore = m[1][n][1];
        firstDelEdge = 1; //一开始断开的边,初始化为第一条边
        for (int i = 2; i <= n; i++){
            if(bestScore < m[i][n][1]){
                bestScore = m[i][n][1];
                firstDelEdge = i; //如果一开始断开第i边有更优的结果,则更新
            }
        }
        
        System.out.print("\n");
        System.out.println("一开始断开第i条边时可以形成的最大数值:");
        for(int i=1; i<=n; i++){ //一开始断开第i条边所能得到的最大分数
            System.out.println("i=" + i + " " + m[i][n][1]);
        }
        
        System.out.print("\n");
        System.out.println("【第一次应该断开的边为:firstDelEdge=" + firstDelEdge+"】");
        
        
        //3 利用getBestSolution方法算出达到最大数值的时候的断开顺序并输出
        System.out.print("\n");
        getBestSolution(firstDelEdge, n, true);   //把子链m[firstDelEdge][n]要取最大值的时候要断开的点依次压栈。 
        System.out.println("要想得到最大的数断开的顺序为:");
        System.out.println("stack--> "+ firstDelEdge);
        while (!stack.empty()){ //打印在断开第firstDelEdge条边后的最优合并顺序
            System.out.println("stack--> " + String.valueOf(stack.pop()));
        }
        
        System.out.print("\n");
        System.out.println("【按以上断开顺序后得到的数(最大的数值)为:BestScore=" + bestScore+"】"); 
    }

  //************************************************************************************************************************************   
    /**
     * 主函数
     * @param args
     */
    

    public static void main(String[] args){
    	System.out.println("请输入你要输入的边(点)的个数:");
        Scanner scanner = new Scanner(System.in);
        while(scanner.hasNext()){
        	
        	//1 输入流
        	
            int n = scanner.nextInt();
            long[][][] m = new long[n+1][n+1][2];
            char[] op = new char[n+1];
            int[] v = new int[n+1];
            System.out.print("\n");
            System.out.println("请输入边和点:");
            for(int i=1; i<=n; i++){    //i从1开始
                op[i] = scanner.next().charAt(0);
                v[i] = scanner.nextInt();
            }
            
            //2 初始化
            fuck ploygonAgent = new fuck(n, m, op, v);

            for (int i=1; i<=n; i++){                           //初始化m[i][j][0]和m[i][j][1]的第一列
                m[i][1][0] = m[i][1][1] = v[i];
            }
            
            //3 主执行函数
            ploygonAgent.polyMax();
            
        }
        scanner.close();
    }
}
请输入你要输入的边(点)的个数:
5

请输入边和点:
* -5 + -2 * -8 * -5 + 8

一开始断开第i条边时可以形成的最大数值:
i=1 168
i=2 480
i=3 488
i=4 488
i=5 120

【第一次应该断开的边为:firstDelEdge=3】

要想得到最大的数断开的顺序为:
stack--> 3
stack--> 2
stack--> 1
stack--> 5
stack--> 4

【按以上断开顺序后得到的数(最大的数值)为:BestScore=488

3)时间复杂度和空间复杂度

时间复杂度:

O(n*3)
【主要的时间复杂度就在于那填表的三个for】

空间复杂度:

O(n*2)

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!