ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

深度优先搜索 洛谷P1123取数游戏 解题思路

2022-01-02 23:00:44  阅读:165  来源: 互联网

标签:洛谷 int Max pos 取数 mem old P1123 pMax


目录


本人萌新一枚,如果有地方错误的话还请各位看官在评论区留言指正。

(。・∀・)ノ゙

题目

一个N ×M的由非负整数构成的数字矩阵,你需要在其中取出若干个数字,使得取出的任意两个数字不相邻(若一个数字在另外一个数字相邻88个格子中的一个即认为这两个数字相邻),求取出数字和最大是多少。

输入格式
第1行有一个正整数TT,表示了有TT组数据。

对于每一组数据,第一行有两个正整数NN和MM,表示了数字矩阵为NN行MM列。

接下来NN行,每行MM个非负整数,描述了这个数字矩阵。

输出格式
TT行,每行一个非负整数,输出所求得的答案。

样例
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;//Max是当前这组的结果,pMax保存当前这一组DFS中每一条路径的中间结果 
int pos[6*6+5];//用于记录位置信息,pos[i] = 1表示从二维数组D开始位置编号,第i号(从0开始编号)当前是否可被选择 


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]){
			//选择第i个位置
			flag = 1;
			int row, cow;
			row = i/M;
			cow = i-M*row;
			pMax +=  D[row][cow];
			//下面置i的右方,左下方,下方,右下方为0
			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和pos进行回退 
			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;//Max是当前这组的结果,pMax保存当前这一组DFS中每一条路径的中间结果 
int pos[6*6+5];//用于记录位置信息,pos[i] = 1表示从二维数组D开始位置编号,第i号(从0开始编号)当前是否可被选择
//int Counts;

void dfs(int k){
	//Counts++;
	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]){
			//选择第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和pos进行回退 
			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;
	}
	//cout<<Counts<<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

标签:洛谷,int,Max,pos,取数,mem,old,P1123,pMax
来源: https://blog.csdn.net/m0_51183899/article/details/122282005

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有