中文题目,题意就不说了。
不得不说这是一道十分经典的状态压缩DP的题目。
思路: 通过分析可以发现,第i行的格子能不能放大炮仅与第i-1和i-2行的放法有关,而与前面的放法无关,因此,如果我们知道了i-1行和i-2放的状态,那么,我们就可以推出第i行的可行的放法状态。因此可以看出i行的状态由它上面两行决定。
设dp[i][j][k]表示前i行,第i行用第j个状态放,第i-1行用第k个状态放的最大大炮的个数,那么状态转移方程就是:dp[i][j][k] = max(dp[i][j][k],dp[i-1][k][t]+num1[j]),其中num1[j]表示第i行用第j个状态放时,该行放的大炮的个数。通过枚举所有可行的状态则可以求出前n行能放的大炮的最大个数。注意,上述状态转移方程中的j,k,t,分别表示第i,i-1,i-2行的状态,他们不能发生冲突。
上面讲了一下具体的思想和转移方程,下面说说状态压缩,即:我们怎样表示每行放的大炮的状态,用什么表示?
可以看到,题目中列数最大为10,这不得不诱导我们往二进制位上面考虑。一个int型数长度位32位 > 10,那么我们可以用一个整数的二进制位中第i位表示该某行第i列放与不放,为1表示已放置大炮,为0表示未位置大炮,一个整数完全可以表示出所有状态,事实上由于一行中的不同列放置大炮之间的距离必须不小于2,因此10列最多60个可行状态,而不是2^10个状态,也就是说dp数组中的j与k最大为60,可以另开一个数组来记录某行可行状态(跟离散化思想挺像的),数组中下标为j的数对应第j个状态,这样不至于导致dp数组开得太大超内存,也优化了时间。同时再记录下在状态j下,该状态中含有1的个数,即表示放的大炮的个数。
最后再说下位运算技巧:
1.给出状态j(对应的数为x)怎样得到该状态中1的个数?可以这么干: x &= (x-1),ret++; 其中x&(x-1)表示每次删去二进制中最后面那个1.
2.怎样知道某个数x表示的状态是否可行? 看 x & (x << 1) 和 x & (x << 2)是否都是0即可。
其他部分用的位运算都比较容易理解,比如置位x |= (1 << bit),检验状态是否冲突x & y 等等,就不一一说了。
#include<cstdio> #include<string> #include<cstring> #include<iostream> #include<algorithm> using namespace std; int n, m, top; int dp[111][70][70], map[111]; int status[70], num1[70]; int calNum1(int x){ int ret = 0; while(x){ret++, x &= (x-1);} return ret; } bool canPlace(int x){ if(x & (x << 1)) return false; if(x & (x << 2)) return false; return true; } void init(){ top = 0; int UP = 1 << m; for(int i = 0;i < UP;i ++){ if(canPlace(i)){ status[++top] = i; num1[top] = calNum1(i); } } } int main(){ char str[111]; /* freopen("in.c", "r", stdin); */ while(~scanf("%d%d", &n, &m)){ memset(str, 0, sizeof(str)); memset(map, 0, sizeof(map)); memset(dp, 0, sizeof(dp)); for(int i = 1;i <= n;i ++) { scanf("%s", str); for(int j = 0;j < m;j ++) if(str[j] == 'H') map[i] |= (1 << j); } init(); for(int i = 1;i <= top;i ++){ if(map[1] & status[i]) continue; dp[1][i][1] = num1[i]; } for(int i = 2;i <= n;i ++){ for(int j = 1;j <= top;j ++){ if(map[i] & status[j]) continue; for(int k = 1;k <= top;k ++){ if(status[j] & status[k]) continue; for(int t = 1;t <= top;t ++){ if(status[j] & status[t]) continue; dp[i][j][k] = max(dp[i][j][k], dp[i-1][k][t]+num1[j]); } } } } int ans = 0; for(int i = 1;i <= top;i ++) for(int j = 1;j <= top;j ++) ans = max(ans, dp[n][i][j]); printf("%d ", ans); } return 0; }