1. 程式人生 > >hdu 2167 方格取數 【狀壓dp】(經典)

hdu 2167 方格取數 【狀壓dp】(經典)

取出 fff ack 分析 題目 經典 gets bsp ets

<題目鏈接>

題目大意:

給出一些數字組成的n*n階矩陣,這些數字都在[10,99]內,並且這個矩陣的 3<=n<=15,從這個矩陣中隨機取出一些數字,在取完某個數字後,該數字周圍8個點都不能取,問:取得數字的最大和為多少?

解題分析:

由於對每一個數,有選和不選兩種可能,分別對應狀態壓縮中的1和0,且 n<=15,1<<15不是非常大,因此就可以非常自然的想到狀態壓縮。

此題要與普通的狀壓dp不同的是,當某一行取某種方案時,如何求出這種取數的所有取得的數之和,就是下面的bit數組,還有要註意一下輸入。

#include<cstdio>                  //
此題要掌握的是,當某一行取某種方案時,如何求出這種取數的所有取得的數之和,就是下面的bit數組 #include<iostream> //還要註意如何讀入的格式,挺難的 #include<cstring> #include <algorithm> #define N (1<<15)+10 int dp[20][N];//dp表示第i行j方案時獲得的最大值 int a[20][20], t[N], cnt, n, bit[N];//cnt存合法方案總數 using namespace std; void init() //
此題要掌握的技巧是,當某一行取某種方案時,如何求出這種取數的所有取得的數之和,就是下面的bit數組 { bit[1] = 1; for (int i = 2; i <= n; i++) //存下2的1~15次方,與之後的狀態比較 bit[i] = bit[i - 1] << 1; //bit[2]為2的1次方,bit[3]為2的二次方 } void solve() { int i, j, k, l; cnt = 0; memset(dp, 0, sizeof(dp)); for (i = 0; i< (1
<< n) - 1; i++) //用二進制存方案,每行最多有(1<<n)-1中方案,包括不合法方案 { if (((i << 1)&i) == 0) //判斷方案i是否相鄰兩格均為1 { for (j = 1; j <= n; j++) if ((bit[j] & i)) //這個技巧一定要掌握,非常妙*** dp[1][i] += a[1][j]; //先處理第一行的所有方案 dp[1][i]表示第一行取第i種方案時,在第一行得到的所有數的總數 t[++cnt] = i; //這裏的t[]數組也同時表示每一行的合法情況(只用一行中選取的數不相鄰這一條件來約束時) } } //初始化dp數組,為下面的狀態轉移方程做好遞推準備 for (i = 2; i <= n; i++) { for (j = 1; j <= cnt; j++) { int posn = 0; for (k = 1; k <= n; k++) if (bit[k] & t[j]) posn += a[i][k]; for (l = 1; l <= cnt; l++) //這裏的 t[j]&t[l]就是用來判斷第i行取j方案時,是否與它上一行取的數,有相鄰的,如果有,則這種方案舍棄 if ((t[j] & t[l]) == 0 && ((t[l] << 1)&t[j]) == 0 && (t[l] >> 1 & t[j]) == 0) // t[l]<<1 & t[j] 和 t[l]>>1&t[j] 則是分別判斷第i行取的數是否與它右上和左上的數相鄰,如果相鄰,也舍棄 dp[i][t[j]] = max(dp[i][t[j]], posn + dp[i - 1][t[l]]); } //dp[i][t[j]]表示當第i行選第j種方案時,前i行能取的數的最大總和 } } int main()//狀態壓縮dp,狀態最多有(2<<15)-1種 { int i, j; char str[100]; while (gets_s(str)) { memset(a, 0, sizeof(a)); n = 0; for (i = 0; i < strlen(str); i += 3) { a[1][++n] = (str[i] - 0) * 10 + (str[i + 1] - 0); } for (i = 2; i <= n; i++) { gets_s(str); int m = 1; for (j = 0; j < strlen(str); j += 3) { a[i][m++] = (str[j] - 0) * 10 + (str[j + 1] - 0); } } getchar(); //以上為讀入矩陣,這個輸入還是要註意 init(); solve(); int res = 0; for (i = 1; i <= cnt; i++) if (res<dp[n][t[i]]) //dp[i][j]表示當第i行選第j種方案時,前i行能取的數的最大總和 res = dp[n][t[i]]; printf("%d\n", res); } return 0; }

2018-07-26

hdu 2167 方格取數 【狀壓dp】(經典)