解题报告:
1.算法 :
(1)首先,看到这个题目想到的是暴力搜索,无所谓深搜还是宽搜,都需要对所有的情况进行穷举,10*100 的格子,这样穷举的话基本上会超时。想到用贪心法,但是贪心法的结果是不正确的。
(2)于是想到动态规划,动态规划的重点是找状态转移方程,需要状态记录的数组 f(因为最终要求的是大炮个数,所以 f 的值记录当前状态的大炮个数)。从题目的数据中来看因为 m<10,所以以行划分状态比较合理,这样有 n 行,就用记录状态的数组 f 的第一个下标记录行号。因为本题的大炮的射程是 2 格,所以,第 i 行和他的前两行都有关系,也就是说第 i 行的状态要根据前两行的状态来得到而不是前一行,于是在记录状态的时候要记录两行的状态(思考为什么),这样, f 数组的第二个下标和第三个下标分别是第 i 行的状态和第 i-1 行的状态。这样可以列出状态转移的方程式: f[i][x][y]=max{f[i -1][y][z]}+c[x] (其中 z 为 i-2 行所有的状态,c[x]为 x 状态中的大炮的个数)
(3)有个状态转移方程这个精髓,就来说说这个题目的实现了:
a.读入数据 n,m 和矩阵数据,并且把每一行的数据以 2 进制的形式(有山处为 1,平原处为0)保存为一个 int 类型,即 row 数组。(read_in 函数)
b.因为同一行的大炮不能相互射击,所以可以把每一行可能的状态列举出来,s 表示一行的所有状态,c 与 s 对应,表示对应状态的大炮个数,s_sum 是 s 的大小,即总的状态个数(m最大为 10 时,一行的状态最多,为 60 种,于是,数组只要开到 60)这些都是为下面的计算节省时间。计算一行可能的所有状态是通过深搜实现的,即 dfs 函数。(creat_s 函数)
**c.最重要的动态规划环节,首先判断特例,只有一行时的做法是:将 s 中所有的状态分别和第一行的状态 row[0]做&操作,以判断是否这个状态可以符合只在平原上放大炮的条件。如果&的结果为 0,即没有在有山的地方放置大炮,反之则不然。在符合条件的状态中找一个大炮数最多的状态作为返回值。如果不止一行,就对 f[1][][]的数组进行操作,第一参数为 1表示后面两个参数是第 1 行和第 0 行的状态,对第 1 行的状态(i)和第 0 行的状态(j)遍历,如果两行均符合平原山地条件以及两行不冲突条件(即两行之间没有大炮能相互攻击),则将f[1][i][j]的值设为 c[i]+c[j],否则为 0。接着要做的事情是从 i=2 开始,通过状态转移方程得到后面的所有状态。最后找出最后两行状态的 f 的最大值,这个值即为所求结果。
2.实现
(1)对于位操作,如果要获得第 i 位的数据,判断((data&(0X<<i))==0),若真,为 0,假,为 1;如果要设置第 i 位为 1, data=(data|(0X1<<i));如果要设置第 i 位为 0, data=(data&(~(0X1<<i)));如果要改变第 i 位,data=(data^(0X1<<i)).
(2)在构建 s 的时候,通过递归进行深搜。对于深搜的代码要熟练掌握,主要是递归条件和终止条件。
(3)动态规划的时候要注意将特殊情况列出,比如这一题的 n=1 的情况。
(4)在多重循环的时候要注意剪枝的运用,如 if((s[x]&row[i])!=0)cont inue;这一句就避免了下面的两重循环的时间浪费。
#include <iostream> #include <cstring> #include <cstdio> using namespace std; int Graph[110];//地形的状态压缩数 int cur_status[65];//因为同一行的大炮不能相互射击,所以可以把每一行可能的状态列举出来,cur_status 表示一行的所有状态 int cur_status_num[65];//表示对应状态的大炮个数,cur_status_num 是 cur_status 的大小,即总的状态个数 int dp[110][65][65];//dp的值记录当前状态的大炮个数 int MAX(int a, int b) { return a > b ? a : b; } int main() { int T; char tmp; int i, j, k, l; int row, col; cin>>T; while (T--) { memset(Graph, 0, sizeof(Graph)); memset(cur_status, 0, sizeof(cur_status)); memset(cur_status_num, 0, sizeof(cur_status_num)); memset(dp, 0, sizeof(dp)); cin>>row>>col; if (row == 0) { cout<<"0"<<endl; continue ; } // 图的压缩PHPP 可以用二进制0100表示,用十进制存储为4。 for (i = 0; i < row; ++i) { for (j = 0; j < col; ++j) { cin>>tmp; if(tmp == 'H') Graph[i] += (1 << j); } } //同地图状态压缩,对排列阵型的状态进行压缩,并算出相应阵型的数量。 //如PHPP有0001 0010 1000 1001 摆法,相应的压缩为 1 2 6 7 相应的炮数为 1 1 1 2 int s_sum = 0; for (i = 0; i < (1 << col); ++i)//初始时找出所有的可能的(合法的)状态,在一个n*m的炮兵地形下每行最多有1<<m种状态,s[i]记录状态为i时s[i]中1的个数 { int num = i; if(i & (i<<1) || i & (i<<2))//判断在该行的状态下,是否存在两个炮兵互相在炮兵射程内 continue ; cur_status_num[s_sum] = num%2;//记录在该合法状态下有多少炮兵 while (num = (num>>1)) cur_status_num[s_sum] += num%2; cur_status[s_sum++] = i; } //动态规划状态转移方程: //dp[i][j][k] = max{dp[i-1][k][l]+cur_status[j]}, //dp[i][j][k]表示第i行状态为cur_status[j],第i-1行状态为cur_status[k]的最大炮兵数 //枚举l的每种状态,且cur_status[j],cur_status[k],cur_status[l],地形互不冲突 //第一行炮兵的放置情况 for (i = 0; i < s_sum; ++i) { if(cur_status[i] & Graph[0]) continue ; dp[0][i][0] = cur_status_num[i]; } //第二行炮兵放置情况 for (i = 0; i < s_sum; ++i)//在第一行的所有合法状态中,找出满足与地形相对应的状态 { if(cur_status[i] & Graph[1]) continue ; for (j = 0; j < s_sum; ++j)//在第0行找出与第1行满足条件的状态 { if(cur_status[j] & Graph[0]) continue ; if(cur_status[i] & cur_status[j]) continue ; dp[1][i][j] = MAX(dp[1][i][j], dp[0][j][0] + cur_status_num[i]); } } for (i = 2; i < row; ++i)//找出第i行满足条件的状态 { for (j = 0; j < s_sum; ++j)//在所有第i行合法状态中,找出满足本题条件的状态 { if(cur_status[j] & Graph[i])//查看第i行的地形与预测的合法状态,是否满足条件,是否在高地放置了大炮 continue ; for (k = 0; k < s_sum; ++k)//找出第i-1行与第i行没有冲突的状态 { if(cur_status[k] & Graph[i-1])//找出第i-1行不与地形相矛盾的状态 continue ; if(cur_status[j] & cur_status[k])//第i行的状态状态不与第i-1行状态相矛盾 continue ; for (l = 0; l < s_sum; ++l)//找出不与第i-2行相冲突的状态 { if(cur_status[l] & Graph[i-2])//找出第i-2行所预测的合法状态不与地形相冲突。 continue ; if((cur_status[k] & cur_status[l]) || (cur_status[j] & cur_status[l]))//找出满足条件第i-1行状态不与第i-2行状态相冲突的状态 continue ; dp[i][j][k] = MAX(dp[i][j][k], dp[i-1][k][l] + cur_status_num[j]); } } } } int MAX_NUM = 0; for (i = 0; i < s_sum; ++i) { for (j = 0; j < s_sum; ++j) { if(dp[row-1][i][j] > MAX_NUM) MAX_NUM = dp[row-1][i][j]; } } cout<<MAX_NUM<<endl; } return 0; }
#include <iostream> #include <cstring> #include <cstdio> using namespace std; int Graph[110];//地形的状态压缩数 int cur_status[65];//因为同一行的大炮不能相互射击,所以可以把每一行可能的状态列举出来,cur_status 表示一行的所有状态 int cur_status_num[65];//表示对应状态的大炮个数,cur_status_num 是 cur_status 的大小,即总的状态个数 int dp[110][65][65];//dp的值记录当前状态的大炮个数 int MAX(int a, int b) { return a > b ? a : b; } int main() { int T; char tmp; int i, j, k, l; int row, col; cin>>T; while (T--) { memset(Graph, 0, sizeof(Graph)); memset(cur_status, 0, sizeof(cur_status)); memset(cur_status_num, 0, sizeof(cur_status_num)); memset(dp, 0, sizeof(dp)); cin>>row>>col; if (row == 0) { cout<<"0"<<endl; continue ; } // 图的压缩PHPP 可以用二进制0100表示,用十进制存储为4。 for (i = 0; i < row; ++i) { for (j = 0; j < col; ++j) { cin>>tmp; if(tmp == 'H') Graph[i] += (1 << j); } } //同地图状态压缩,对排列阵型的状态进行压缩,并算出相应阵型的数量。 //如PHPP有0001 0010 1000 1001 摆法,相应的压缩为 1 2 6 7 相应的炮数为 1 1 1 2 int s_sum = 0; for (i = 0; i < (1 << col); ++i)//初始时找出所有的可能的(合法的)状态,在一个n*m的炮兵地形下每行最多有1<<m种状态,s[i]记录状态为i时s[i]中1的个数 { int num = i; if(i & (i<<1) || i & (i<<2))//判断在该行的状态下,是否存在两个炮兵互相在炮兵射程内 continue ; cur_status_num[s_sum] = num%2;//记录在该合法状态下有多少炮兵 while (num = (num>>1)) cur_status_num[s_sum] += num%2; cur_status[s_sum++] = i; } int MAX_NUM = 0; //动态规划状态转移方程: //dp[i][j][k] = max{dp[i-1][k][l]+cur_status[j]}, //dp[i][j][k]表示第i行状态为cur_status[j],第i-1行状态为cur_status[k]的最大炮兵数 //枚举l的每种状态,且cur_status[j],cur_status[k],cur_status[l],地形互不冲突 //第一行炮兵的放置情况 for (i = 0; i < s_sum; ++i) { if(cur_status[i] & Graph[0]) continue ; dp[0][i][0] = cur_status_num[i]; if(dp[0][i][0] > MAX_NUM) MAX_NUM = dp[0][i][0]; } //第二行炮兵放置情况 for (i = 0; i < s_sum; ++i)//在第一行的所有合法状态中,找出满足与地形相对应的状态 { if(cur_status[i] & Graph[1]) continue ; for (j = 0; j < s_sum; ++j)//在第0行找出与第1行满足条件的状态 { if(cur_status[j] & Graph[0]) continue ; if(cur_status[i] & cur_status[j]) continue ; dp[1][i][j] = MAX(dp[1][i][j], dp[0][j][0] + cur_status_num[i]); if(dp[1][i][j] > MAX_NUM) MAX_NUM = dp[1][i][j]; } } for (i = 2; i < row; ++i)//找出第i行满足条件的状态 { for (j = 0; j < s_sum; ++j)//在所有第i行合法状态中,找出满足本题条件的状态 { if(cur_status[j] & Graph[i])//查看第i行的地形与预测的合法状态,是否满足条件,是否在高地放置了大炮 continue ; for (k = 0; k < s_sum; ++k)//找出第i-1行与第i行没有冲突的状态 { if(cur_status[k] & Graph[i-1])//找出第i-1行不与地形相矛盾的状态 continue ; if(cur_status[j] & cur_status[k])//第i行的状态状态不与第i-1行状态相矛盾 continue ; for (l = 0; l < s_sum; ++l)//找出不与第i-2行相冲突的状态 { if(cur_status[l] & Graph[i-2])//找出第i-2行所预测的合法状态不与地形相冲突。 continue ; if((cur_status[k] & cur_status[l]) || (cur_status[j] & cur_status[l]))//找出满足条件第i-1行状态不与第i-2行状态相冲突的状态 continue ; dp[i][j][k] = MAX(dp[i][j][k], dp[i-1][k][l] + cur_status_num[j]); if(dp[i][j][k] > MAX_NUM) MAX_NUM = dp[i][j][k]; } } } } /** int MAX_NUM = 0; for (i = 0; i < s_sum; ++i) { for (j = 0; j < s_sum; ++j) { if(dp[row-1][i][j] > MAX_NUM) MAX_NUM = dp[row-1][i][j]; } } */ cout<<MAX_NUM<<endl; } return 0; }