HDU 1693 Eat the Trees (插頭dp)

題目:http://acm.hdu.edu.cn/showproblem.php?pid=1693

題意:

這是一道插頭dp的入門題

給出一個n*m的地圖,標記爲1的格子上有樹,有個英雄要吃掉所有的樹,吃樹的時候要遵循規則:

1)一圈一圈地吃(只能走上下左右,每個圈至少4個格子,頭尾相連);

2)只能走有樹的格子,吃過的樹就消失了,即圈不能相交,每個格子只能走1次;

3)可以分成幾個圈來吃。

思路:

1)概念

【插頭】每個格子有四條邊,每條邊上可以插一個插頭(與邊垂直)。因爲每個格子只能經過一次,即在每個格子中選一條邊進一條邊出,每個格子必插兩個插頭。

【輪廓線】

輪廓線形狀:

dp[ i ][ j ][ k ] 表示的輪廓線經過第 i 行第 j 列格子的下邊和右邊

如dp[1][2][k]的輪廓線:


給輪廓線經過的每條邊編號,每條邊爲一個插頭位置

用過狀態壓縮枚舉狀態k,如

0 1 2 3 4

1 0 1 1 1    表示第0,2,3,4條邊上的插頭插上了, 如下


2)步驟

從左上角開始,一格一格枚舉狀態(輪廓線在經該格的右邊和下邊)

狀態k下,x爲該格的下邊,y爲右邊

    若xy邊都有插頭,則該格的左邊和上邊沒有插頭

    若xy邊都沒插頭,則該格的左邊和上邊都有插頭

    若x,y邊中只有1邊有插頭,則該格的另一個插頭在左邊或上邊上

即確定了xy邊的狀態,可以得到對應的格子左邊和上邊的狀態



(紅色爲dp[ i ][ j ]輪廓線,藍色爲dp[ i ][ j - 1 ]輪廓線)

觀察序號之間的關係可以發現

dp[ i ][ j ][ k1 ] 要找對應的左邊和上邊狀態

即找dp[ i ][ j - 1 ][ k2 ],使得k1和k2,除了x和y位,其他位都一樣,所以可以由k1狀態通過位運算輕鬆表示出對應的k2狀態


還需注意,初始化 dp[i][0]時,dp[i][0][k << 1] = dp[i - 1][m][k];

可由下圖看出



代碼:

#include <stdio.h>
#include <string.h>
#include <math.h>
#include <algorithm>
#define MOD 1000000007
#define INF 0x7fffffff
using namespace std;
typedef long long ll;
int cell[15][15];
ll dp[15][15][5005];

int main()
{
    #ifdef LOCAL
    freopen("dpdata.txt", "r", stdin);
    #endif

    int t, n, m;
    scanf("%d", &t);
    for(int cas = 1; cas <= t; cas++)
    {
        scanf("%d%d", &n, &m);
        for(int i = 1; i <= n; i++)
            for(int j = 1; j <= m; j++)
                scanf("%d", &cell[i][j]);
        memset(dp, 0, sizeof(dp));
        dp[0][m][0] = 1;
        for(int i = 1; i <= n; i++)
        {
            for(int k = 0; k < (1 << m); k++)
            { //初始化決策到第i行第0格時的狀態
                dp[i][0][k << 1] = dp[i - 1][m][k];
            }
            for(int j = 1; j <= m; j++)
            {
                for(int k = 0; k < (1 << (m + 1)); k++)
                {
                    int y = 1 << j, x = 1 << (j - 1);
                    if(cell[i][j])
                    { //這格可到達
                        if((k & x) && (k & y))
                            dp[i][j][k] = dp[i][j - 1][k - x - y];
                        else if(!(k & x) && !(k & y))
                            dp[i][j][k] = dp[i][j - 1][k + x + y];
                        else //k^x^y異或運算使得x和y插了變成不插,沒插變成插
                            dp[i][j][k] = dp[i][j - 1][k] + dp[i][j - 1][k ^ x ^ y];
                    }
                    else
                    {
                        if(!(k & x) && !(k & y))
                            dp[i][j][k] = dp[i][j - 1][k];
                        else
                            dp[i][j][k] = 0;
                    }
                }
            }
        }
        printf("Case %d: There are %I64d ways to eat the trees.\n", cas, dp[n][m][0]);
    }

    return 0;
}


發佈了77 篇原創文章 · 獲贊 10 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章