sgu138狀壓dp用dfs來統計

題目大意:有一個大小爲N * M的蛋糕,蛋糕上面有k根蠟燭,現在要求你在蛋糕上面鋪1 * 2和2 * 1的巧克力 
使得所鋪的巧克力最少,且蛋糕上面沒有空餘地方可放巧克力了(只存在1 * 1的沒鋪的方格)

解題思路:1 * 1的空閒方格由三行決定,上一行,當前行和下一行,如果只考慮兩行的話,就比較難了 
所以我們用dp[i][s1][s2]表示第i行的狀態是s1,第i + 1行的狀態是s2的情況下放的最少巧克力數量 
如此的話,可得到遞推方程 
dp[i][s1][s2] = min(dp[i][s1][s2], dp[i-1][s3][s4] + cnt) 
解釋上面方程的意思:在第i-1行的狀態是s3,第i行的狀態是s4的情況下,在第i行和第i+1上鋪巧克力,使第i行的狀態變成s1,第i+1行的狀態變成s2,統計出鋪在第i行和第i+1行的巧克力數量cnt,這樣的話,狀態轉移就完成了
現在的問題是如何更新,更新第i行的話有可能有影響到第i+1行,所以傳入兩個狀態,第i行和第i+1行的狀態,然後dfs暴力枚舉出放的巧克力,統計一下即可 
這裏在更新第N行的時候,需要用到第N+1行,又因爲第N+1行是外界的行,所以最後的答案是 
min(dp[N][s1][0]),第N+1行不能被佔用,如果被佔用,就表示放的巧克力超出邊界了

代碼:
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

const int INF = 0x3f3f3f3f;

int n, m;
char str[75][10];
int dp[2][130][130];
int s[75];
int tmpps, tmpns, pre, now;

void dfs(int ps, int ns, int nts, int ni, int cnt) {
    if (ni > 0 && ((ps>>(ni - 1))&1) == 0 && ((ns>>(ni - 1))&1) == 0) return;//檢測i-1行與i行是不是滿足題意,橫着放或者豎着放
    if (ni > 1 && ((ns>>(ni - 1))&1) == 0 && ((ns>>(ni - 2))&1) == 0) return;//如果不滿足就直接退出就好了
    if (ni == m) {
        dp[pre^1][ns][nts] = min(dp[pre^1][ns][nts], dp[pre][tmpps][tmpns] + cnt);
        return;
    }
    dfs(ps, ns, nts, ni + 1, cnt);//如果第i-1行與第i行配合就已經把第i行的ni位置搞定了就接着往下搜,而不必使用第i+1行了。
    //這兩個是如果上面所說的情況不符合,那麼就要用到i+1行了,橫着放和豎着放兩種情況,然後在往下搜索。
    if (ni < m - 1 && ((ns>>ni)&1) == 0 && ((ns>>(ni + 1))&1) == 0) dfs(ps, ns|(1<<ni)|(1<<(ni + 1)), nts, ni + 2, cnt + 1);
    if (((ns>>ni)&1) == 0 && ((nts>>ni)&1) == 0) dfs(ps, ns|(1<<ni), nts|(1<<ni), ni + 1, cnt + 1);
}

int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++) {
        scanf("%s", str[i]);
        for (int j = 0; j < m; j++)
            if (str[i][j] == '*') s[i] |= (1<<j);
    }
    int ss = (1<<m);
    pre = 0, now = 1;
    for (int i = 0; i < ss; i++)
        for (int j = 0; j < ss; j++)
            dp[now][i][j] = INF;
    dp[now][ss - 1][s[1]] = 0;
    for (int k = 1; k <= n; k++) {
        swap(now, pre);
        for (int i = 0; i < ss; i++)
            for (int j = 0; j < ss; j++)
                dp[now][i][j] = INF;
        for (int i = 0; i < ss; i++) {
            if (i&s[k - 1] != s[k - 1]) continue;//i中可以包含s,也就是說s是0的位置,i中可以是1
            for (int j = 0; j < ss; j++) {
                if (j&s[k] != s[k]) continue;//這個也是同理
                if (dp[pre][i][j] == INF) continue;//第i-1行還能放,就直接跳過
                tmpps = i; tmpns = j;//記錄現在枚舉的這兩個狀態
                dfs(i, j, s[k + 1], 0, 0);
            }
        }
    }
    int ans = INF;
    for (int i = 0; i < ss; i++)
        ans = min(ans, dp[now][i][0]);
    printf("%d\n", ans);
    return 0;
}

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