(算法问题)贪吃蛇游戏改编版

Problem:
Jeff loves playing games, Gluttonous snake( an old game in NOKIA era ) is one of his favourites. However, after playing gluttonous snake so many times, he finally got bored with the original rules.In order to bring new challenge to this old game, Jeff introduced new rules :
1.The ground is a grid, with n rows and m columns(1 <= n, m <= 500).
2.Each cell contains a value v (-1<=vi<=99999), if v is -1, then this cell is blocked, and the snakecan not go through, otherwise, after the snake visited this cell, you can get v point.
3.The snake can start from any cell along the left border of this ground and travel until it finally stops at one cell in the right border.
4.During this trip, the snake can only go up/down/right, and can visit each cell only once.

Special cases :
a. Even in the left border and right border, the snake can go up and down.
b. When the snake is at the top cell of one column, it can still go up, which demands the player to pay all current points , then the snake will be teleported to the bottom cell of this column and vice versa.

After creating such a new game, Jeff is confused how to get the highest score. Please help him to write a program to solve this problem.

Input
The first line contains two integers n (rows) andm (columns), (1 <= n, m <= 500), separated by a single space.
Next n lines describe the grid. Each line contains m integers vi (-1<=vi<=99999) vi = -1 means the cell is blocked.
Output
Output the highest score you can get. If the snake can not reach the right side, output -1.Limits

Sample Test
Input

4 4
-1 4 5 1
2 -1 2 4
3 3 -1 3
4 2 1 2

output

23

Sample Test
Input

4 4
-1 4 5 1
2 -1 2 4
3 3 -1 -1
4 2 1 2

output

16

算法的基本思想:
注意:从某一点离开所能获得的最大分数 不等同于 从某一列离开所能获得的最大分数
问题要求解的是蛇到达最右边一列时,所能获取的最大分数,可以停在最右列任意一点。根据题意,蛇在水平方向上只能向右。蛇在每一列上,都要经过两次决策。即从该列的哪一点进入,从哪一点离开。对于这两个问题,如果只从贪婪策略上考虑,即只关注在第i列获取最大分数,显然是不够的,而且会存在无法通过的路径。我们需要转换角度来看待这个问题,第i+1列某点所能获取的最大分数是与第i列的出点相关的,而第i列某点所能获取的最大分数是与其后的列无关的。并且如果第i+1列某点想要得到在此点离开所能获得最大分数,那么无论是从第i列的哪一点进入第i+1列,从该点离开时的分数也一定是该点所能获得最大分数。形式化的描述如下:
假设路径P=(p0,p1,p2,p2,….,pi,….pn)表示取得最大分数M的路径,pi(i != 0)表示第i列的出点,p0表示第1列的入点。那么路径S=(p0,p1,p2,p2,….,pi)一定是从pi点离开所能获取的最大分数的路径。否则即可以找到另一条到达pi点的路径NS,使得离开pi点的分数更高,那么就有路径NP={NS,….,pn},使得NP所获得分数大于M(反证法证明结论成立)。
因此,将原问题转换为新问题后,即求解从最右列的某点离开时所能得到的最大分数。新问题具有最优子结构,可以使用动态规划求解新问题。假设网格为n行m列,用score[i][j]表示在第j列时,从点p[i][j]离开此列所能获取的最大分数。则DP方程如下:

  score[k][j+1] = max{score[i][j] + sum}(0<i<n) 
 (sum表示从第p[i][j+1]点到p[k][j+1]所能获取的总分数;如果需要穿过边界,则取score[i][j]=0)  

可以求得最右列的每一个点作为出点时的最大分数序列,从中选其最大者,即为原问题的解。

代码(java):

public class Main {
    static int n; // ground row
    static int m; // ground column
    static int grid[][] = new int[500][500]; // game ground
    static int score[][]; // score[i][j] means highest score at grid[i][j]
    static int dis[][][]; // dis[j][x][y] means the sum which can be scored from
                            // grid[x][j] to grid[y][j]

    static void readInput() throws IOException {
        BufferedReader r = new BufferedReader(new InputStreamReader(System.in));
        String s = r.readLine();
        n = Integer.parseInt(s.substring(0, s.indexOf(' ')));
        m = Integer.parseInt(s.substring(s.indexOf(' ') + 1, s.length()));
        for (int i = 0; i < n; i++) {
            s = r.readLine();
            String[] a = s.split(" ");
            for (int j = 0; j < m; j++) {
                grid[i][j] = Integer.parseInt(a[j]);
            }
        }
    }

    static void computeDis() {
        dis = new int[m][n][n];
        for (int j = 0; j < m; j++) {
            for (int x = 0; x < n; x++) {
                dis[j][x][x] = grid[x][j];
                boolean reachable = (grid[x][j] != -1);
                for (int y = x + 1; y < n; y++) {
                    if (reachable && grid[y][j] != -1) {
                        dis[j][x][y] = dis[j][x][y - 1] + grid[y][j];
                    } else {
                        reachable = false;
                        dis[j][x][y] = -1;
                    }
                    dis[j][y][x] = dis[j][x][y];
                }
            }
        }
    }

    static void computeScore() {
        score = new int[n][m];

        // compute the first column
        for (int x = 0; x < n; x++) {
            score[x][0] = dis[0][x][x];
            for (int y = 0; y < n; y++) {
                score[x][0] = (dis[0][x][y] > score[x][0]) ? dis[0][x][y]
                        : score[x][0];
            }
        }

        // compute other columns
        for (int j = 1; j < m; j++) {
            for (int i = 0; i < n; i++) {
                score[i][j] = -1;
                for (int k = 0; k < n; k++) {
                    if ((dis[j][k][i] != -1) && (score[k][j - 1] != -1)) {
                        int temp = score[k][j - 1] + dis[j][k][i];
                        score[i][j] = (temp > score[i][j]) ? temp : score[i][j];
                    }
                }
            }
        }
    }

    static int highestScore() {
        int max = -1;
        for (int i = 0; i < n; i++) {
            max = score[i][m - 1] > max ? score[i][m - 1] : max;
        }
        return max;
    }

    public static void main(String[] args) {
        try {
            readInput();
            computeDis();
            computeScore();
            System.out.println(highestScore());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

时间复杂度
算法部分最外层循环计算按列递推,内层对于某一列中每一个点,都要计算从前一列中任一点达到此点所能获取的分数,因此时间复杂度为O(mn2)。

空间复杂度
我这里多用了个辅助数组,实际空间复杂度为O(nm)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值