题解 HDU1565 【方格取数(1)】
给你一个n*n的格子的棋盘,每个格子里面有一个非负数。
从中取出若干个数,使得任意的两个数所在的格子没有公共边,就是说所取的数所在的2个格子不能相邻,并且取出的数的和最大。
题目清晰明了,这道题应该用dp。
自然地想到$dp[i][j]$表示位置$(i, j)$的最大值,但是在状态转移的时候推不出来。
这是因为子状态缺少表示选择情况的维度,即:你不知道某一个特定的点有没有被取。
因此自然地想到$dp[i][j][s]$其中$s$储存着一个状态。
但是经过简单的计算就可以发现,时间复杂度过高,即便这道题有5000ms的时限都妥妥的TLE。
因此我们需要状态压缩。
所以想到了$dp[i][s]$其中$i$表示第$i$行,而$s$表示这一行的选择状态,用一个二进制数储存。(0为留下1为取走)
思考一下不难得出状态转移方程:
1 dp[i][s1] = max(dp[i][s1], dp[i - 1][s2] + tmp);
其中$tmp$为第$i$行在$s1$状态下的值,$dp$数组初始化也是一样的。
要注意,题目中说明了格子不能相邻,所以在预处理的时候可以凭此缩小枚举范围。(不缩小会炸)
大致思路就是这样了,具体的一下优化因人而异。有一个要注意的点就是杭电最近会出玄学结果,刚开始本蒟蒻开小了数组导致越界,结果结果是WA,害得调了很久才发现。
完整AC代码如下:(好像是495ms吧)
1 #include <iostream> 2 #include <cstdio> 3 #include <cmath> 4 #include <algorithm> 5 #include <cstring> 6 #include <vector> 7 using namespace std; 8 9 inline int read() { 10 11 } 12 13 const int maxn = 21; 14 15 int n; 16 int sq[maxn][maxn]; 17 int top[maxn], ok[20000]; 18 19 void init() { 20 int tp = 0, co = 1; 21 for(int i = 0; i < (1 << (maxn - 1)); i++) { 22 if(!(i&(i<<1))) { 23 ok[tp++] = i; 24 // cout << i << endl; 25 } 26 if(i == (1 << co) - 1) top[co++] = tp; 27 } 28 // cout << top << endl; 29 return ; 30 } 31 32 int dp[maxn][20000]; 33 34 int value(int line, int state) { 35 int ans = 0, pos, t = 0; 36 while(state) { 37 pos = state % 2; 38 if(pos == 1) ans += sq[line][t]; 39 t++; 40 state /= 2; 41 } 42 return ans; 43 } 44 45 void dp_init() { 46 // cout << top << endl; 47 for(int i = 0; i < n; i++) { 48 for(int s = 0; s < top[n]; s++) { 49 dp[i][s] = value(i, ok[s]); 50 // cout << dp[i][s] << ' '; 51 } 52 // cout << endl; 53 } 54 return ; 55 } 56 57 void solve() { 58 for(int i = 1; i < n; i++) { 59 for(int s1 = 0; s1 < top[n]; s1++) { 60 int tmp = dp[i][s1]; 61 for(int s2 = 0; s2 < top[n]; s2++) { 62 if(!((ok[s1] | ok[s1] << 1 | ok[s1] >> 1) & ok[s2])) { 63 dp[i][s1] = max(dp[i][s1], dp[i - 1][s2] + tmp); 64 } 65 } 66 } 67 } 68 return ; 69 } 70 71 int main() { 72 init(); 73 // for(int i = 1; i <= 20; i++) cout << top[i] << ' '; 74 // cout << endl; 75 while(scanf("%d", &n) != EOF) { 76 for(int i = 0; i < n; i++) { 77 for(int j = 0; j < n; j++) { 78 scanf("%d", &sq[i][j]); 79 } 80 } 81 dp_init(); 82 solve(); 83 int ans = 0; 84 for(int i = 0; i < top[n]; i++) { 85 ans = max(ans, dp[n - 1][i]); 86 } 87 printf("%d\n", ans); 88 } 89 return 0; 90 }
题外话:洛谷上好像有一道类似的紫题(网络流24题的),只不过不是正方形是长方形,而且实现也只有1000ms。