深度优先搜索 dfs
1. [HDU -1258]** -----Sum It Up
Problem Description
Given a specified total t and a list of n integers, find all distinct sums using numbers from the list that add up to t. For example, if t=4, n=6, and the list is [4,3,2,2,1,1], then there are four different sums that equal 4: 4,3+1,2+2, and 2+1+1.(A number can be used within a sum as many times as it appears in the list, and a single number counts as a sum.) Your job is to solve this problem in general.
Input
The input will contain one or more test cases, one per line. Each test case contains t, the total, followed by n, the number of integers in the list, followed by n integers x1,…,xn. If n=0 it signals the end of the input; otherwise, t will be a positive integer less than 1000, n will be an integer between 1 and 12(inclusive), and x1,…,xn will be positive integers less than 100. All numbers will be separated by exactly one space. The numbers in each list appear in nonincreasing order, and there may be repetitions.
Output
For each test case, first output a line containing ‘Sums of’, the total, and a colon. Then output each sum, one per line; if there are no sums, output the line ‘NONE’. The numbers within each sum must appear in nonincreasing order. A number may be repeated in the sum as many times as it was repeated in the original list. The sums themselves must be sorted in decreasing order based on the numbers appearing in the sum. In other words, the sums must be sorted by their first number; sums with the same first number must be sorted by their second number; sums with the same first two numbers must be sorted by their third number; and so on. Within each test case, all sums must be distince; the same sum connot appear twice.
Sample Input
4 6 4 3 2 2 1 1
5 3 2 1 1
400 12 50 50 50 50 50 50 25 25 25 25 25 25
0 0
Sample Output
Sums of 4:
4
3+1
2+2
2+1+1
Sums of 5:
NONE
Sums of 400:
50+50+50+50+50+50+25+25+25+25
50+50+50+50+50+25+25+25+25+25+25
分析
解题
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,m,a[15],num[15],b[15],flag;//num[]数组保存第一项到该项的和;b[]数组保存相加等于目标和的路径。flag判断是否有可能解
void dfs(int x,int y,int sum)//x:第一个值,y:控制保存路径的项数
{
if(num[m]-num[x-1]<sum)//当第x-1项到最后一项的和小于剩余目标和,直接返回
return;
if(sum==0) //当目标和剩余0时,说明这一路径成立,输出。
{
flag=1; //flag=1
printf("%d",b[1]);
for(int i=2; i<y; i++)
printf("+%d",b[i]);
printf("\n");
return;
}
for(int i = x; i <= m; ++i) //从x项到最后一项
{
if(a[i]<=sum) //只有某项小于目标和剩余才进行处理
{
b[y]=a[i];//放入b[]数组中
dfs(i+1,y+1,sum-a[i]);//处理下一项到最后一项
while(i+1<=m&&a[i]==a[i+1])//如果相邻两个值相等(因为是排过序的),为了避免产生相同的结果,跳过下一个值
i++;
}
}
}
int main()
{
while(cin>>n>>m)
{
if(n==0&&m==0) break;
flag=0;
memset(b,0,sizeof(b));
num[0]=0;
for(int i=1; i<=m; i++)
cin>>a[i];
sort(a+1,a+1+m);
for(int i=1; i<=m; i++)
num[i]=num[i-1]+a[i];
printf("Sums of %d:\n",n);
if(num[m]<n){
printf("NONE\n");
continue;
}
dfs(1,1,n);
if(flag==0)
printf("NONE\n");
}
return 0;
}
n皇后问题相关
洛谷P1219
检查一个如下的6 x 6的跳棋棋盘,有六个棋子被放置在棋盘上,使得每行、每列有且只有一个,每条对角线(包括两条主对角线的所有平行线)上至多有一个棋子。
上面的布局可以用序列2 4 6 1 3 5来描述,第i个数字表示在第i行的相应位置有一个棋子,如下:
行号 1 2 3 4 5 6
列号 2 4 6 1 3 5
这只是跳棋放置的一个解。请编一个程序找出所有跳棋放置的解。并把它们以上面的序列方法输出。解按字典顺序排列。请输出前3个解。最后一行是解的总个数。
//以下的话来自usaco官方,不代表洛谷观点
特别注意: 对于更大的N(棋盘大小N x N)你的程序应当改进得更有效。不要事先计算出所有解然后只输出(或是找到一个关于它的公式),这是作弊。如果你坚持作弊,那么你登陆USACO Training的帐号删除并且不能参加USACO的任何竞赛。我警告过你了!
输入输出格式
输入格式:
一个数字N (6 <= N <= 13) 表示棋盘是N x N大小的。
输出格式:
前三行为前三个解,每个解的两个数字之间用一个空格隔开。第四行只有一个数字,表示解的总数。
样例
6
2 4 6 1 3 5
3 6 2 5 1 4
4 1 5 2 6 3
4
分析
n 皇后问题可以转化为全排列问题,对于第 i 列,ans[ i ] 保存其在该列皇后所在行数。
所以这种做法不用考虑棋盘中在一行和一列会不会出现多个皇后,主要考虑对角线。
我们用 c[ ] 数组表示 " / " 方向对角线 ,用 d [ ] 数组表示 " \ " 方向对角线。
c[ x+i ] 效果
d [ x - i + n ] 效果
解题
#include<iostream>
#include<cstring>
#include<cmath>
#include<cstdio>
using namespace std;
const int MA=100;
//分别表示行,两条对角线的数组
int b[MA],c[MA],d[MA];
//对于第 i 列,ans[ i ] 保存其在该列皇后所在行数
int ans[MA];
//total表示总结果数,n为棋盘长,宽。
int total,n;
//输出函数
void out()
{
//题目要求只输出三种结果
if(total<=2){
for(int k=1;k<=n;k++)
cout<<ans[k]<<" ";
cout<<endl;
}
//只要调用一次 out() 函数,就说明有了一种结果,总结果数增加。
total++;
}
//形参 x 表示列数,即第x列
void dfs(int x)
{
//结束条件 :处理到棋盘边界
if(x>n){
out();
return;
}
//对于每一列,(i 从第一行遍历到第 n 行) 判断第 i 行能不能放皇后。
for(int i=1;i<=n;++i){
//如果这一行没有放过皇后(即 b[ i ] != 0),并且两条对角线上没有
//皇后( 即 c[x+i] != 0 && d[ x-i+n] != 0 ),就在该处放皇后。
if(!b[i]&&!c[x+i]&&!d[x-i+n]){
b[i]=1;//第 i 行有皇后
ans[x]=i;//第 x 列皇后放在第 i 列。
c[x+i]=1;//对角线" / "
d[x-i+n]=1;//对角线" \ "
dfs(x+1);//放下一列的皇后
//一种可能处理完后,将b[],c[],d[],ans[]清零。
b[i]=0;
c[x+i]=0;
d[x-i+n]=0;
ans[x]=0;
}
}
}
int main()
{
cin>>n;
total=0;
memset(b,0,sizeof(b));
memset(c,0,sizeof(c));
memset(d,0,sizeof(b));
dfs(1);
printf("%d\n",total);
return 0;
}
迷宫问题相关
洛谷P1605
题目
迷宫 【问题描述】
给定一个N*M方格的迷宫,迷宫里有T处障碍,障碍处不可通过。给定起点坐标和
终点坐标,问: 每个方格最多经过1次,有多少种从起点坐标到终点坐标的方案。在迷宫
中移动有上下左右四种方式,每次只能移动一个方格。数据保证起点上没有障碍。
【数据规模】
1≤N,M≤5
【输入】
第一行N、M和T,N为行,M为列,T为障碍总数。第二行起点坐标SX,SY,终点
坐标FX,FY。接下来T行,每行为障碍点的坐标。
【输出】
给定起点坐标和终点坐标,问每个方格最多经过1次,从起点坐标到终点坐标的方
案总数。
【样例】
输入:
2 2 1
1 1 2 2
1 2
输出:
1
分析
从起始点开始(sx,sy),依此向四个方向移动(这是在不越界,未走过的前提下),再以下一个点为中心超它的四个方向移动。直到终点,路线数 total++,返回到上一个点。
注意:
每次到一个点,如果该点未走过就把该点标记为走过的点(vis[ 该点的行坐标 ] [ 该点的纵坐标 ]=1),如果这个点四个方向都已经不能走了,就返回上一个点,特别强调此时要把这个点的标记改为未走过,因为这一路线虽然不用这个点了,但可能之后的路线会用到这个点。
解题
#include<iostream>
#include<cstring>
#include<cmath>
#include<cstdio>
using namespace std;
const int MA=10;
int N,M,T,sx,sy,ex,ey,fx,fy,total;
int vis[MA][MA];//标记点是否走过
//dfs
void dfs(int sx,int sy,int ex,int ey){
//结束条件1.越界或这个点已经走过了
if(sx<1 || sx>N || sy<1 || sy>M || vis[sx][sy])return;
//结束条件2.到达终点,路线数++
if(sx==ex && sy==ey){
total++;
return;
}
//该点之前未走过,先在在这个点上,标记为走过
vis[sx][sy]=1;
//超四个方向走(if语句判断是否走到边界)
if(sx < N)dfs(sx+1,sy,ex,ey);
if(sx > 1)dfs(sx-1,sy,ex,ey);
if(sy < M)dfs(sx,sy+1,ex,ey);
if(sy > 1)dfs(sx,sy-1,ex,ey);
//四个方向都处理完后会返回到这里,此时这个点以后的路线都处理完了,所以要退回上一个点。
//对上一个个点的处理可能还每完,所以这个点以后可能还会用上,退出这个点前将标记改为未走过。
vis[sx][sy]=0;
return;
}
int main()
{
memset(vis,0,sizeof(vis));
cin>>N>>M>>T;//行数,列数,障碍数
cin>>sx>>sy>>ex>>ey;//起始坐标和终点坐标
for( int i = 0 ; i < T ; ++ i )
{
cin>> fx >> fy;
vis[fx][fy]=1;//标记障碍
}
total=0;
dfs(sx,sy,ex,ey);
cout<<total<<endl;
return 0;
}
单词方阵,找其中的某段特定的字符串
洛谷P1605
题目
给一n \times nn×n的字母方阵,内可能蕴含多个“yizhong”单词。单词在方阵中是沿着同一方向连续摆放的。摆放可沿着 88 个方向的任一方向,同一单词摆放时不再改变方向,单词与单词之间可以交叉,因此有可能共用字母。输出时,将不是单词的字母用*代替,以突出显示单词。例如:
输入输出格式
输入格式:
第一行输入一个数n。(7≤n≤100)。
第二行开始输入n \times nn×n的字母矩阵。
输出格式:
突出显示单词的n \times nn×n矩阵。
样例(1)
7
aaaaaaa
aaaaaaa
aaaaaaa
aaaaaaa
aaaaaaa
aaaaaaa
aaaaaaa
输出(注:输出没有 “ , ” )
样例(2)
8
qyizhong
gydthkjy
nwidghji
orbzsfgz
hhgrhwth
zzzzzozo
iwdfrgng
yyyygggg
输出
分析
遍历二维数组,找出所有 ‘ y ’点(因为这些点是开头,当然也可以找 ‘ g ’ 点 );
以这些点为中心,向 8 个方向寻找满足题意的字符串。
解题
#include<iostream>
#include<cstring>
#include<cmath>
#include<cstdio>
using namespace std;
const int MA=105;
int b[MA][MA];//标记这个点是否要输出‘ * ’
char s[MA][MA];//图像
char p[MA]={'y','i','z','h','o','n','g'};//目标字符串
int tx,ty,n,flag;//n : 图像长,宽大小,flag:实现<如果一个方向不合题意,处理下一个方向>的功能
int xx[9]={1,1,1,0,0,-1,-1,-1};
int yy[9]={1,0,-1,1,-1,0,1,-1};//8 个方向控制
void dfs(int sx,int sy)
{
for(int i=0;i<8;++i){ //遍历 8 个方向
flag=0;
for(int j=1;j<=6;++j){//沿着这一方向移动点
tx=sx+j*xx[i];
ty=sy+j*yy[i];
if(tx<1||tx>n||ty<1||ty>n){//移动时不能越界
flag=1;
break;
}
if(p[j]!=s[tx][ty]){//如过这一方向不是目标字符串就处理下一个方向
flag=1;
break;
}
}
if(flag==1)continue;//不合题意,处理下一个方向
for(int j=0;j<=6;++j){
//如果满足,就在 b[] 数组中标记这些点,让最后可以输出这些点的值。
tx=sx+j*xx[i];
ty=sy+j*yy[i];
b[tx][ty]=1;
}
}
return;
}
int main()
{
cin>>n;
for(int i = 1 ;i <= n ; ++ i){
//输入图像
for(int j = 1 ;j <= n ; ++ j){
cin>>s[i][j];
}
}
memset(b,0,sizeof(b));
for(int i=1;i<=n;++i){
//寻找 ‘ y ’点,并处理。
for(int j=1;j<=n;++j){
if(s[i][j]=='y'){
dfs(i,j);
}
}
}
for(int i = 1 ; i <= n ; ++ i){
//输出结果 ,b[]数组标记过的点输出其本身值,其它输出‘ * ’ ;
for(int j = 1 ; j <= n ; ++ j){
if(b[i][j]==1)
cout<<s[i][j];
else
cout<<"*";
}
cout<<endl;
}
return 0;
}