本人萌新一枚,如果有地方错误的话还请各位看官在评论区留言指正。
(。・?・)ノ゙
题目
一个N ×M的由非负整数构成的数字矩阵,你需要在其中取出若干个数字,使得取出的任意两个数字不相邻(若一个数字在另外一个数字相邻88个格子中的一个即认为这两个数字相邻),求取出数字和最大是多少。
输入格式 第1行有一个正整数T,表示了有T组数据。
对于每一组数据,第一行有两个正整数N和M,表示了数字矩阵为N行M列。
接下来N行,每行M个非负整数,描述了这个数字矩阵。
输出格式 T行,每行一个非负整数,输出所求得的答案。
样例 3 4 4 67 75 63 10 29 29 92 14 21 68 71 56 8 67 91 25 2 3 87 70 85 10 3 17 3 3 1 1 1 1 99 1 1 1 1
样例结果 271 172 99
题解
DFS 每一个dfs()选取一个数,到无可选的数时,比较当前当前结果pMax和最终结果Max,更新Max
一些问题: 1.先选0号位再选3号位,和先选3号位再选0号位实际上是一样的效果,如何避免不必要的计算? 先选低号位,再选高号位,也可以看作是一个剪枝。 实现:父节点选择一个数后,把该数后面一个位置i传给子节点参数,子节点从i开始选择,不可向前选择 2.父节点如何把信息(哪些位置不可选)传递给子结点? 引入全局数组pos[]记录各个位置是否可选,为1表示可选,为0表示不可选。 父函数选择一个数后对pos[]进行更改,再调用子函数,需要注意的是,由于我采用的是全局变量(pos),从子函数返回后要对父函数本次对pos的操作进行回退,采用全局变量的好处是递归时减少传参耗费的时间。
AC代码
#include <iostream>
#include <string.h>
#include <algorithm>
#include <vector>
using namespace std;
int T;
int N, M;
int D[6+1][6+1];
int Max, pMax;
int pos[6*6+5];
void dfs(int k){
if(k >= N*M){
Max = max(Max, pMax);
return;
}
int flag = 0;
for(int i=k; i<N*M; i++){
if(pos[i]){
flag = 1;
int row, cow;
row = i/M;
cow = i-M*row;
pMax += D[row][cow];
int mem[4] = {0};
int old[4] = {0};
if(i+1<N*M && (i+1)/M == row){
old[0] = pos[i+1];
pos[i+1] = 0;
mem[0] = 1;
}
if(i+M-1<N*M && (i+M-1)/M == row+1){
old[1] = pos[i+M-1];
pos[i+M-1] = 0;
mem[1] = 1;
}
if(i+M < N*M){
old[2] = pos[i+M];
pos[i+M] = 0;
mem[2] = 1;
}
if(i+M+1<N*M && (i+M+1)/M == row+1){
old[3] = pos[i+M+1];
pos[i+M+1] = 0;
mem[3] = 1;
}
dfs(i+1);
pMax -= D[row][cow];
if(mem[0]){
pos[i+1] = old[0];
}
if(mem[1]){
pos[i+M-1] = old[1];
}
if(mem[2]){
pos[i+M] = old[2];
}
if(mem[3]){
pos[i+M+1] = old[3];
}
}
}
if(!flag){
Max = max(Max, pMax);
}
}
void init(){
Max = pMax = 0;
for(int i=0; i<N*M; i++){
pos[i] = 1;
}
}
void gmn(){
init();
dfs(0);
}
int main(int argc, char *argv[]) {
cin>>T;
for(int i=0; i<T; i++){
cin>>N>>M;
for(int i=0; i<N; i++){
for(int j=0; j<M; j++){
cin>>D[i][j];
}
}
gmn();
cout<<Max<<endl;
}
return 0;
}
改进
本题由于数据范围比较小,对递归次数的要求并不是很高,实际上还可以进行剪枝,比如在选择几个数据之后,可能会在这几个数之间留下空隙,例如: 数据: 23 21 34 87 22 24 34 33 98 35 33 88 98 78 67 32 78 19 23 76 假如依次选择23、22、33、98、76,这样的话,第一行第三列的34就是可选但没有选的数,由于本题求的是最大的总和,那么该方法求的结果必然不是最大的。 我改进的想法:定期对是否存在空隙进行检查,如果存在则直接剪枝,由于每一行至多影响到上一行,也就是说每一行至多填满上一行的空隙,无法填满之前的,所以可以在每一次调用dfs(i)时,都对i所在行的上一行的上一行进行检测,若找到空隙则进行剪枝。 由于我之前取数只是对该数后面的位置进行pos=0的操作,所以采用该方法时,要对该数所在的九宫格的所有数的位置都进行pos=0.
改进代码
#include <iostream>
#include <string.h>
#include <algorithm>
#include <vector>
using namespace std;
int T;
int N, M;
int D[6+1][6+1];
int Max, pMax;
int pos[6*6+5];
void dfs(int k){
if(k >= N*M){
Max = max(Max, pMax);
return;
}
int r = k/M;
if(r-2>=0){
r -= 2;
for(int i=0; i<M; i++){
if(pos[r*M+i]){
return;
}
}
}
int flag = 0;
for(int i=k; i<N*M; i++){
if(pos[i]){
flag = 1;
int row, cow;
row = i/M;
cow = i-M*row;
pMax += D[row][cow];
int mem[8] = {0};
int old[8] = {0};
if(i+1<N*M && (i+1)/M == row){
old[0] = pos[i+1];
pos[i+1] = 0;
mem[0] = 1;
}
if(i+M-1<N*M && (i+M-1)/M == row+1){
old[1] = pos[i+M-1];
pos[i+M-1] = 0;
mem[1] = 1;
}
if(i+M < N*M){
old[2] = pos[i+M];
pos[i+M] = 0;
mem[2] = 1;
}
if(i+M+1<N*M && (i+M+1)/M == row+1){
old[3] = pos[i+M+1];
pos[i+M+1] = 0;
mem[3] = 1;
}
pos[i] = 0;
if(i-1 >= 0 && (i-1)/M == row){
old[4] = pos[i-1];
pos[i-1] = 0;
mem[4] = 1;
}
if(i-M-1 >= 0 && (i-M-1)/M == row-1){
old[5] = pos[i-M-1];
pos[i-M-1] = 0;
mem[5] = 1;
}
if(i-M >= 0){
old[6] = pos[i-M];
pos[i-M] = 0;
mem[6] = 1;
}
if(i-M+1 >= 0 && (i-M+1)/M == row-1){
old[7] = pos[i-M+1];
pos[i-M+1] = 0;
mem[7] = 1;
}
dfs(i+1);
pMax -= D[row][cow];
if(mem[0]){
pos[i+1] = old[0];
}
if(mem[1]){
pos[i+M-1] = old[1];
}
if(mem[2]){
pos[i+M] = old[2];
}
if(mem[3]){
pos[i+M+1] = old[3];
}
pos[i] = 1;
if(mem[4]){
pos[i-1] = old[4];
}
if(mem[5]){
pos[i-M-1] = old[5];
}
if(mem[6]){
pos[i-M] = old[6];
}
if(mem[7]){
pos[i-M+1] = old[7];
}
}
}
if(!flag){
Max = max(Max, pMax);
}
}
void init(){
Max = pMax = 0;
for(int i=0; i<N*M; i++){
pos[i] = 1;
}
}
void gmn(){
init();
dfs(0);
}
int main(int argc, char *argv[]) {
cin>>T;
for(int i=0; i<T; i++){
cin>>N>>M;
for(int i=0; i<N; i++){
for(int j=0; j<M; j++){
cin>>D[i][j];
}
}
gmn();
cout<<Max<<endl;
}
return 0;
}
改进前测试用例需要调用dfs()360次,改进后只用了276次,其实改进的并不多(lll¬ω¬),大家有什么更好的方法可以在评论区一起交流一下,本萌新很乐意回复哦(??????)??
经验总结
做题的时候回退那个地方一开始有错,卡了半天,被这个全局变量的方法坑到了,进行回退的时候,如果当前函数置pos[i]=0,我就回退为pos[i] = 1,实际上pos[i]可能一开始为0,置pos[i]=0后又回退为1就发生了错误,应该是回退为0. 这也给我留了个教训o(╥﹏╥)o:回退时如果是A=B的形式一定要慎重考虑,A之前是否为B
|