CSP-homework Week3 ProblemA DFS两题--选数问题和八皇后问题详解(C++)

总述

这道的题目主要涉及的是DFS思想,给出求解过程的同时也将另一道经典DFS+剪枝问题-------八皇后问题给出了求解过程。

题目一:选数问题

原题叙述

Given n positive numbers, ZJM can select exactly K of them that sums to S. Now ZJM wonders how many ways to get it!

INPUT

The first line, an integer T<=100, indicates the number of test cases. For each case, there are two lines. The first line, three integers indicate n, K and S. The second line, n integers indicate the positive numbers.

OUTPUT

For each case, an integer indicate the answer in a independent line.

输入样例

1
10 3 10
1 2 3 4 5 6 7 8 9 10

输出样例

4

题目重述

题意比较简单,每次输入n个数字,从这T个数字的输入样例里面,选择K个数字(每个数字只能选择一次),使他们的和相加等于一个值S,求解一共有多少种这种组合的情况。
整个求解过程共执行T次,即对上述的求解过程执行T次。

解题思路

思路概述

DFS思想的板子题,使用一个数组来存储每一个点是否被访问过,在DFS过程中做如下的一些处理:(注:已选个数chosen_num,已选值的总和chosen_value,个数限值K,目的值S)

情况 采取的操作
chosen_num==K && chosen_value==S 组合个数+1
chosen_num<K && chosen_value>=S 剪枝丢弃
chosen_num<K&& chosen_value<S 向下进行DFS

采用这种策略就可以将所有情况枚举出来

数据存储

使用

int store[20];

来存储每个点的访问情况,初始化将所有点置为0,DFS的过程中如果访问过就置为1
要注意的是:继续向下DFS的部分,如果将数字a加入到选择的数中,store[a]置为1后,在完成向下DFS的递归之后,将store[a]重置为0,这样可以保证在同一层选择数值时,store数组中的访问情况是完全相同的。
代码如下:

for(int i=begin;i<positive_number;i++)
           {
           	if(store[i]==0)
			{
           	   store[i]=1;
               dfs(number+1,addtion+store_number[i],i+1);
           	}
           	store[i]=0;//保证同层选择新数时,访问数组store[]情况完全一致
           }

题目一总结

一道比较简单的Dfs板子题,但是要注意的是,如果在一次向下dfs后不添加访问数组重置的操作,会导致dfs过程出现较大的问题,在手写时在此处出现了错误,导致调试了几次才成功accept。

题目一改进点

暂无

题目一源码

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
int store_number[20];
int store[20];//存放每个点是否被访问过
int positive_number=0;//可供选择的数字个数
int add_number=0;//累计的数字个数
int num_addition=0;//累计的数字和
int sum_kind=0;//组合种类的个数
void dfs(int number,int addtion,int begin)
{
    if(number==add_number && addtion==num_addition)
    {
        sum_kind++;
    }
    else if(number<add_number)
    {
       if(addtion>=num_addition)
       return;
       else
       {
           for(int i=begin;i<positive_number;i++)
           {
           	if(store[i]==0)
			{
           	   store[i]=1;
               dfs(number+1,addtion+store_number[i],i+1);
           	}
           	store[i]=0;
           }
       }
    }
    
}
int main()
{
    int bfs_times=0;
    cin>>bfs_times;
    for(int i=0;i<bfs_times;i++)
    {
    	for(int k=0;k<20;k++)
    	{
    		store[k]=0;
		}
        cin>>positive_number>>add_number>>num_addition;
        for(int j=0;j<positive_number;j++)
            cin>>store_number[j];
        dfs(0,0,0); 
        cout<<sum_kind<<endl;
        sum_kind=0;
    }
}

题目二:八皇后问题

原题叙述

八皇后问题是一道DFS的经典题目,大概题目叙述如下:
在国际象棋中,皇后的攻击范围是本行本列和以改点为中心的对角线。现有一个8×8的国际象棋棋盘,尝试在这个棋盘上摆放开8个皇后,使她们不互相攻击,如果有多个解,尝试输出全部解。

OUTPUT

输出全部可行的结果。

输出样例

一个可行结果的输出样例:

1 0 0 0 0 0 0 0
0 0 0 0 1 0 0 0
0 0 0 0 0 0 0 1
0 0 0 0 0 1 0 0
0 0 1 0 0 0 0 0
0 0 0 0 0 0 1 0
0 1 0 0 0 0 0 0
0 0 0 1 0 0 0 0

题目重述

八皇后问题是一个图上的搜索问题,找到每一种情况,要满足八个皇后都摆放在棋盘上,且每个皇后所在的行列和对角线都没有其他皇后,输出所有的情况。

解题思路

思路概述

从第0行开始摆放皇后,每摆放一个新的位置,对这个摆放的位置进行检验,查看这个位置本行本列以及对角线有无已经摆放的皇后,如果没有说明合法,向下进行DFS,不合法则将该点重置,并尝试本行的下一个点,直至摆满8行,输出摆放的方案。
对于检查函数,由于使用较多,将其封装出来,代码见下:

bool check_right(int x, int y, int map[8][8])
{
	int left_x, left_y, right_x, right_y = 0;
	if (x == 0)
	{
		return true;
	}
	for (int i = 0; i < x; i++)
	{
		if (map[i][y] == 1)
		{
			return false;
		}
	}
	left_x = x-1; left_y = y-1;
	right_x = x-1; right_y = y+1;
	while (left_x >= 0 && left_y >= 0)
	{
		if (map[left_x][left_y] == 1)
		{
			return false;
		}
		left_x = left_x - 1;
		left_y = left_y - 1;
	}
	while (right_x >= 0 && right_y < 8)
	{
		if (map[right_x][right_y] == 1)
		{
			return false;
		}
		right_x = right_x - 1;
		right_y = right_y + 1;
	}
	return true;
}

数据存储

使用二维数组map[][]来存放整个棋盘上每个位置是否摆放了皇后。初始化置为0,如果在该位置摆放皇后是合法的,就置为1.
与上面题目1同样注意的点是,map数组在试点置为1并完成合法性验证操作后,要对该点进行重置,保证在同层进行DFS时,棋盘的情况是完全一致的。重置部分关键代码如下:

for (int the_col = 0; the_col < 8; the_col++)
	{
		map[row][the_col] = 1;
		if (check_right(row, the_col, map))
		{
			if (row == 7)
				print(map);
			else
				dfs(row + 1, map);
		}
		map[row][the_col] = 0;
	}

题目二总结

对两道题进行了手写并剖析其特点后,可以总结出DFS类题型的通用特点:
1、维护一个记录是否访问过该点的数据结构,可能是一维数组或者高位数组,具体形式与题意相关。
2、在向下DFS的过程中,每找到一个新点,要对其进行合法性的判断,合法则继续向下并在记录访问的数组中打上标记,不合法则将该点跳过进行下一个点。
3、在同层进行点的访问,选取时,如果找到了某个合法点并向下DFS,在该层下一次选取新点前,要将本次对记录访问数组做出的修改做重置,保证同层选取新点时,记录访问数组的取值是完全一致的。

题目二改进点

对于八皇后问题的优化是一个长期被讨论的问题,其中比较常见的有在除第一行外的其他行摆放新的棋子时,对已有的情况进行查看,不需要再尝试那些本列或本对角线上已经有皇后的点,即可行性剪枝,来最大化减少枚举的次数,后续会对源码进行相应的优化并实时更新优化位置。若有高见,请慷慨指教。

题目二源码

#include<iostream>
#include<stdio.h>
#include<algorithm>
#include<string>
#include<string.h>
using namespace std;
int number = 0;
bool check_right(int x, int y, int map[8][8])
{
	int left_x, left_y, right_x, right_y = 0;
	if (x == 0)
	{
		return true;
	}
	for (int i = 0; i < x; i++)
	{
		if (map[i][y] == 1)
		{
			return false;
		}
	}
	left_x = x-1; left_y = y-1;
	right_x = x-1; right_y = y+1;
	while (left_x >= 0 && left_y >= 0)
	{
		if (map[left_x][left_y] == 1)
		{
			return false;
		}
		left_x = left_x - 1;
		left_y = left_y - 1;
	}
	while (right_x >= 0 && right_y < 8)
	{
		if (map[right_x][right_y] == 1)
		{
			return false;
		}
		right_x = right_x - 1;
		right_y = right_y + 1;
	}
	return true;
}
void print(int map[8][8])
{
	number++;
	cout<<number<<":" <<endl;
	for (int i = 0; i < 8; i++)
	{
		for (int j = 0; j < 8; j++)
		{
			if(map[i][j]==0)
			cout<<"0"<<" ";
			else
			cout<<"1"<<" "; 
		}
		cout << endl;
	}
}
void dfs(int row, int map[8][8])
{
	for (int the_col = 0; the_col < 8; the_col++)
	{
		map[row][the_col] = 1;
		if (check_right(row, the_col, map))
		{
			if (row == 7)
				print(map);
			else
				dfs(row + 1, map);
		}
		map[row][the_col] = 0;
	}
}
int main()
{
	int map[8][8];
	memset(map, 0, sizeof(map));
	dfs(0, map);
	cout <<"八皇后问题的解决方案数量有:"<< number<<"个";
	return 0;
} 
发布了17 篇原创文章 · 获赞 2 · 访问量 1663

猜你喜欢

转载自blog.csdn.net/qq_43942251/article/details/104757882