(算法問題)貪喫蛇遊戲改編版

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)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章