深度优先搜索DFS与记忆化搜索

深度优先搜索(DFS)

求连通块 HDOJ-1241 Oil Deposits

【题目】石油勘探公司把油田分成许多的大格,每个大格又分为许多小格,然后分析各个小格是否有石油矿藏。有矿藏的小格(用@表示)称为容器. 如果2个容器相连(横、竖、斜), 则它们是同一矿区的不同部分。输入各大格的矿藏分布(无矿藏用*表示),输出其中有多少个不同的矿区。

#include
using namespace std;
char g[105][105]={0};
int m,n;
int dir[8][2]={{-1,-1},{-1,0},{-1,1},{0,-1},{0,1},{1,-1},{1,0},{1,1}};
void dfs(int x,int y){
	if(x<0||x==m||y<0||y==n||g[x][y]=='*') return;//越界或走过,则返回
	g[x][y]='*';//标记走过
	for(int k=0;k<8;k++)//查找下一级可用点
		dfs(x+dir[k][0],y+dir[k][1]);
}
int main(){	
	while(cin>>m>>n&&m){//m行n列
		int i,j;
		for(i=0;i<m;i++)//建议从0开始,便于整行输入
			cin>>g[i];
		int num=0;
		for(i=0;i<m;i++)
			for(j=0;j<n;j++)
				if(g[i][j]=='@')//仅在第一级递归计数,故分离
					{num++;dfs(i,j);}
		cout<<num<<endl;
	}
	return 0;
}

HDOJ-1031 N皇后问题

【题目】在N×N的方格棋盘放置了N个皇后,使得它们不相互攻击(即任意2个皇后不允许处在同一排,同一列,也不允许处在与棋盘边框成45角的斜线上。输入给定的N(N≤10),输出有多少种合法的放置方法。

【思路】在N×N棋盘中摆N个皇后,意味着每行有一个皇后,每列有一个皇后,每条主对角线有一个皇后,每条副对角线有一个皇后;换言之,每个皇后控制着一行、一列。一条主对角线、一条副对角线。因此,以每行对应一次循环,用a,b,c三个数组存储各主对角线、副对角线、列的占用情况。点(x,y)位于第x行,第y列,第y-x+n条主对角线(1-n<=y-x<=n-1,处理使数组从1开始),第x+y条副对角线(2<=x+y<=2n)。

#include
#include
using namespace std;
int n,so;
bool a[25],b[15],c[25];//a代表主对角线,b代表列,c代表副对角线
void dfs(int i){
	if(i>n) {so++;return;}
	for(int j=1;j<=n;j++){//每列
		if(a[j-i+n]||b[j]||c[i+j]) continue;//不可用,则跳过
		a[j-i+n]=true;b[j]=true;c[i+j]=true;//标记控制区域
		dfs(i+1);//查找下一行可用点
		a[j-i+n]=false;b[j]=false;c[i+j]=false;//取消标记
	}
}
int main(){
	int ans[15]={0};
	for(n=1;n<=10;n++){
		memset(a,false,sizeof(a));
		memset(b,false,sizeof(b));
		memset(c,false,sizeof(c));	
		so=0;dfs(1);ans[n]=so;
	}
	while(cin>>n&&n)
		cout<<ans[n]<<endl;
	return 0;
}

HDOJ-1426 Sudoku Killer

【题目】输入一批不完整的数独地图(未知数值用?表示),输出它们的解。(对于每组测试数据保证它有且只有一个解)

【思路】
仔细分析,发现与上一题有类似之处:小格中的每一个数字x,都可以看做是所在行、列、九宫格的数字x的控制数字——这个数字控制着所在行、列、九宫格,在其控制区域内不能出现另一个x。用结构体存储空格的位置,i(0~8)代表小格行标,j(0~8)代表小格列标,则i/3代表九宫格行标,j/3代表九宫格列标。以每个空格对应一层递归,用a,b,c三个数组存储各行、列、九宫格的可用性,数组最后一维代表9个可能的数字,其他维度代表序号。
由题意,每个空格的值不全为9。当从最后一个空格(已填写)出发进行下一层递归时,触发条件空格溢出,记录填写完成,函数返回到倒数第二个空格的下一次循环。填写完成条件满足,循环变量的上一个值填写到真正的数独空格中,函数再返回到倒数第三个空格……对于值为9的情况,在循环之外另行处理。
用两层循环读入数独地图存到字符数组中,读入的字符若是数字,标记控制区域;若是空格,记录其位置坐标。

#include
#include
#include
using namespace std;
char map[9][9];
struct space
	{int i;int j;}s[81];
bool a[9][12],b[9][12],c[3][3][12];//a代表行,b代表列,c代表九宫格
int sn,snm;//sn=Space Number空格编号,snm=空格编号最大值
int done;//done为1代表所有空格填写完成
void dfs(int sn){
	if(sn>snm) {done=1;return;}
	int v;
	for(v=1;v<=9;v++){//每个可能值
		if(done==1) {map[s[sn].i][s[sn].j]='0'+v-1;break;}
		if(a[s[sn].i][v]||b[s[sn].j][v]||c[s[sn].i/3][s[sn].j/3][v]) continue;//不可用,则跳过
		a[s[sn].i][v]=true;b[s[sn].j][v]=true;c[s[sn].i/3][s[sn].j/3][v]=true;//标记控制区域
		dfs(sn+1);//填写下一个空格
		a[s[sn].i][v]=false;b[s[sn].j][v]=false;c[s[sn].i/3][s[sn].j/3][v]=false;//取消标记
	}
	if(v==10&&done==1) map[s[sn].i][s[sn].j]='0'+v-1;
}
int main(){
	char ch;int f=1;
	do{
		int i,j;
		memset(a,false,sizeof(a));
		memset(b,false,sizeof(b));
		memset(c,false,sizeof(c));
		done=0;sn=0;
		for(i=0;i<9;i++){
			for(j=0;j<9;j++){
				cin>>ch;
				if(ch!='?') 
					{a[i][ch-'0']=true;b[j][ch-'0']=true;c[i/3][j/3][ch-'0']=true;}
				else {s[sn].i=i;s[sn].j=j;sn++;}
				map[i][j]=ch;
			}
		}
		snm=sn-1;
		dfs(0);
		if(f==0) cout<<endl; else f=0;
		//第二组数据开始,输出前加一空行
		for(i=0;i<9;i++){
			for(j=0;j<8;j++)
				cout<<map[i][j]<<" ";
			cout<<map[i][j]<<endl;			
		}	
	}while(scanf("%c",&ch)!=EOF);
	return 0;
}

【总结】1031 之前以单个方格作为区域可用性的存储单元,这样一个方格可能被多个皇后控制,故不能用bool标记,而是放上一个皇后将其占用区域与控制区域值加1,取消标记时将皇后将其占用区域与控制区域值减1。在处理射线扩展时,方向数组与越界判断繁复,放弃。参考他人之后,发现利用棋盘长宽等长的条件可以简化问题。

记忆化搜索

【题目】
○题目描述
在一个数阵中,求出最长的严格下降路径的长度(只能向相邻的上下左右走);
如在下面这个数阵中最长的路径为:25->24->23…->1(长度为25)
1 2 3 4 5
16 17 18 19 6
15 24 25 20 7
14 23 22 21 8
13 12 11 10 9
○输入
第一行包含测试用例数T。每个测试用例第1行是名称(字符串)、行数r和列数c。之后是r行,每行有c个数。N和r和c不大于100;
○输出
对于每个测试用例,打印一行,其中包含区域名称、冒号、空格和最长长度。
○样例输入
1
scj 10 5
56 14 51 58 88
26 94 24 39 41
24 16 8 51 51
76 72 77 43 10
38 50 59 84 81
5 23 37 71 77
96 10 93 53 82
94 15 96 69 9
74 0 62 38 96
37 54 55 82 38
○样例输出
scj: 7

【思路】
dp[i][j]存储从a[i][j]开始的最长路径长度,则dp[i][j]=1+max(dp[x][y])
(点(x,y)与(i,j)相邻且不越界,且满足a[i][j]>a[x][y],若不存在这样的点,max值为0)
递推需要遵循线性顺序,而本题涉及路径,故使用记忆化搜索。记忆化搜索涉及递归,原因是搜索过程中调用的结果可能尚未生成,需要针对调用对象展开进一层搜索。
用一个函数来生成dp[i][j]的值,函数执行过程中会更新从(i,j)出发的所有路径上的点的dp值,但路径不一定包含所有点。在遍历所有点之后, a n s = max ⁡ 1 ≤ i ≤ r , 1 ≤ j ≤ c d p [ i ] [ j ] ans=\max_{1≤i≤r,1≤j≤c} dp[i][j] ans=1ir,1jcmaxdp[i][j]

#include
#include
#include
#include
#include
using namespace std;
int r, c;
int a[105][105];
int dp[105][105];
int search(int n, int m){
    int temp = a[n][m];
    if(n - 1 > 0 && a[n-1][m] < temp) {
        if(dp[n-1][m] != 0) dp[n][m] = max(dp[n][m], dp[n-1][m] + 1);
        else dp[n][m] = max(dp[n][m], search(n-1, m) + 1);
    }
    if(n + 1 <= r && a[n+1][m] < temp) {
        if(dp[n+1][m] != 0) dp[n][m] = max(dp[n][m], dp[n+1][m] + 1);
        else dp[n][m] = max(dp[n][m], search(n+1, m) + 1);
    }
    if(m - 1 > 0 && a[n][m-1] < temp) {
        if(dp[n][m-1] != 0) dp[n][m] = max(dp[n][m], dp[n][m-1] + 1);
        else dp[n][m] = max(dp[n][m], search(n, m-1) + 1);
    }
    if(m + 1 <= c && a[n][m+1] < temp) {
        if(dp[n][m+1] != 0) dp[n][m] = max(dp[n][m], dp[n][m+1] + 1);
        else dp[n][m] = max(dp[n][m], search(n, m+1) + 1);
    }
    return dp[n][m];
}

int main() {
    int t;
    scanf("%d", &t);
    while(t--){
        memset(dp, 0, sizeof(dp));
        char ch[105];
        scanf("%s", ch);
        scanf("%d %d", &r, &c);
        for(int i = 1; i <= r; i++) 
            for(int j = 1; j <= c; j++) 
                scanf("%d", &a[i][j]);
        int maxmum = 0;
        for(int i = 1; i <= r; i++) 
            for(int j = 1; j <= c; j++)
                maxmum = max(maxmum, search(i, j));
        printf("%s: ", ch);
        printf("%d\n", maxmum + 1);
    }
    return 0;
}

你可能感兴趣的:(算法,算法,深度优先)