文章目录
总述
这道的题目主要涉及的是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;
}