第九章 递归
9.1 斐波那契数列 见动态规划专题|| 汉诺塔问题
9.2 波兰式求值
dfs() 表示读取一个数字或者运算符并进行相应的atof转换或者递归运算
逆波兰式不需要递归只需要维护一个栈,读到数字就存入(全局)栈中,读到运算符就将栈顶两个元素进行运算,结果存入栈顶
#include<cstdio>
#include<math.h>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<string>
#include<map>
#include<set>
#include<vector>
#define MAX 10
#define num 32767
#define INF 0x7f7f7f
#define eps 1e-5
using namespace std;
double dfs()
{
string s;//读入一个数字或者运算符
cin>>s;
if(s[0]=='+')//对运算符进行递归
return dfs()+dfs();
else if(s[0]=='-')
return dfs()-dfs();
else if(s[0]=='*')
return dfs()*dfs();
else if(s[0]=='/')
return dfs()/dfs();
else//对数字进行转换
return atof(s.data());
}
int main()
{
//freopen("input.txt","r",stdin);
printf("%f\n",dfs());
return 0;
}
小技巧: string类型转换成char *
//string ->char * 需要调用函数
const char *c = s.data();//或者 char *c = (char *)s.data();
const char *c = s.c_str();//或者 char *c = (char *)s.c_str();
//char * -> string 直接复赋值
string s = c;
9.3 递归解决排列组合问题
9.3.1 从起点开始递增的排列组合(可重复或者不可重复,且与顺序无关)
可重复 与顺序无关
注意
1 枚举所有起点(main中的for)
2 枚举所有后继(dfs中的for)
3 dfs(出口参数 , 条件参数1 , 条件参数2…)
出口
剪枝
递增枚举
2749 分解因数(乘法分解)
dfs(int 当前积, int 上一个因子) 表示当积为dangqianji 且上一个因子是shangyigeyinzi的情况下能否完成分解
#include<cstdio>
#include<math.h>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<string>
#include<map>
#define MAX 53
#define num 32767
#define INF 0x7f7f7f
#define eps 1e-5
using namespace std;
int n,a,ans;
void dfs(int k,int pre)//限定条件:递增 由参数pre实现(k用来判递归出口)
{
if(k==a)
{
ans++;
return ;
}
if(k>a) return ;
for( int i=pre;i<=a;i++)//如果是不重复递增则i=pre+1
{
if(k*i<=a)
dfs(k*i,i);
else break;
}
}
int main()
{
//freopen("input.txt","r",stdin);
cin>>n;
for(int i=0;i<n;i++)
{
ans=0;
cin>>a;
for(int i=2;i<=a;i++) dfs(i,i);//枚举所有起点
cout<<ans<<endl;
}
return 0;
}
1664 放苹果(加法分解) 可以看做是和确定的递增排列,且限定了加的次数(上一题因子分解没限定乘的次数,这里递归函数的参数要多加一个)
dfs(int 当前第几个盘 , int 总苹果 ,int 上一个盘放的苹果数) 表示该情况继续分解
#include<cstdio>
#include<math.h>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<string>
#include<map>
#include<set>
#include<vector>
#define MAX 10
#define num 32767
#define INF 0x7f7f7f
#define eps 1e-5
using namespace std;
int m,n,k,t;
void dfs(int dangqianpan , int zongpingguo,int 上一个盘放的苹果数 )//这里多加了一个限定条件(加的次数也就是盘子总数)
{
if(zongpingguo==m&&dangqianpan<n)
{
k++;
return ;
}
if(zongpingguo >m||dangqianpan>n) return;
for(int i=上一个盘放的苹果数;i<=m;i++)
dfs(dangqianpan+1,zongpingguo+i,i);
}
int main()
{
//freopen("input.txt","r",stdin);
cin>>t;
for(int i=0;i<t;i++)
{
cin>>m>>n;
k=0;
for(int i=1;i<=m;i++)//遍历可能的起点
dfs(0,i,i);
cout<<k<<endl;
}
return 0;
}
2803 碎纸机 这里切割的位次相当于一个递增序列
dfs(int 当前总和,int 上一次切的位数)
//定义 vector<int> temp,ans 存临时解和最优解
//输入 int 目标数 ; char 纸片数[MAX];
void dfs(int temp_sum,int index)
{
//出口
if(index>=len)//切出最后一位
{
//更新ans
if(fabs(temp_sum-目标数)<最优解) //复制temp到ans; 方案数=0 ; 更新最优解;
if(==) 方案数++;
return ;
}
//剪枝
if( temp_sum > 目标数 ) return;
//递增枚举下一个切口位置
for(int i=index+1;i<=len;i++)
{
//转换从纸片[index]到纸片[i]的数得到 t
//存储t到temp中
dfs(temp_sum+t,i);
}
}
//主函数
//特判1 每一位加起来和仍大与目标数
//特判2 纸片数整体小于目标数
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
int n,m;
int shu[23333];
int tmp[23333];
bool flag1;
void clr()
{
memset(shu,0,sizeof(shu));
flag1=0;
}
int ans1[23333];
int ans;
int lenth;
void dfs(int tot,int num,int cnt)//当前和,上一次切的第几位,temp中保存了几个数(可以省略)
{
//剪枝
if(tot>n)
return;
//出口
if(num>m)
{
if(tot==ans)
{
flag1=1;
return;
}
if(tot>ans)
{
flag1=0;
ans=tot;
//更新ans
for(int i=1;i<cnt;i++)
ans1[i]=tmp[i];
lenth=cnt-1;
}
return;
}
//递增枚举
int hah=0;
for(int i=num;i<=m;i++)
{
hah*=10;
hah+=shu[i];
tmp[cnt]=hah;
dfs(tot+hah,i+1,cnt+1);
}
}
int main()
{
while(1)
{
clr();
string a;
scanf("%d",&n);
ans=-1;
cin>>a;
int hah=0;
m=a.size();
if(n==0)
break;
for(int i=0;i<m;i++)
{
shu[i+1]=a[i]-'0';
hah*=10;
hah+=shu[i+1];
}
//特判
if(hah==n)
{
printf("%d %d\n",n,n);
continue;
}
dfs(0,1,1);
if(flag1)
{
puts("rejected");
continue;
}
if(ans==-1)
puts("error");
else
{
printf("%d ",ans);
for(int i=1;i<=lenth;i++)
printf("%d ",ans1[i]);
puts("");
}
}
return 0;
}
9.3.2 全排列问题(把1~n按照某种顺序不重复地摆放)
不重复 与顺序有关
注意
1 dfs(递归出口参数) 一般为全排列的总位数n
求n个数的全排列个数 || 求n个数的第k个全排列 可以看作不在同一行同一列的皇后
dfs(int 当前第几行) 表示在当前行放该行的数
//定义 vector<int> ans,temp(临时排列和最终排列); bool visit[MAX];(某个数是否已经加入本次排列)
void dfs(int index)
{
if(index==n+1)//注意是n+1,因为dfs(1)从1开始
{
//计数 或者 按照规则检查temp更新ans(如八皇后)
return;
}
for(int i=1;i<=n;i++)//9.3.1 中因为是递增排列所以不需要一个标记数组visit
{//这里在全排列中需要从头开始找没用过的数字
if(!visit[i])
{
temp.push_back(i);
visit[i]=1;
dfs(index+1);
visit[i]=0;//全排列问题需要还原状态
}
}
}
2754 八皇后问题
dfs(int 当前第几行) 表示 在当前行放该行的数
#include <iostream>
#include<cstdio>
#include<vector>
#include<string>
#include<map>
#include <math.h>
#include<algorithm>
#include <string.h>
#define eps 1e-10
#define ll long long
#define da 0x3f3f3f3f
#define xiao -0x3f3f3f3f
using namespace std;
int b;
vector<int > ans,temp;
bool visit[9]={false};
int num;
void dfs(int index)
{
if(cnt==8)//从0开始所以是n
{
num++;//代表找到了第num个全排列
if(num==b)
ans=temp;
return ;
}
if(index>8) return ;
for(int i=0;i<8;i++)
{
if(!visit[i])
{
//遍历之前的皇后(八皇后问题的高校写法是将检查从出口移动到for中)
//所谓回溯也就是将出口处的判断移动到后面的程序中
bool flag=1;
//八皇后只需要检查之前的皇后是否在k=+-1斜线上,因为不在同一行和同一列是必然的
//index为当前行号,外层for是当前列号,内层for中是之前的行号,temp[]为之前的列号
for(int j=0;j<index;j++)
if(abs(index-j)==abs(i-temp[j])) flag=0;//abs保证k=+-1
if(flag)
{
visit[i]=1;
temp.push_back(i);
dfs(cnt+1);
temp.pop_back();
visit[i]=0;
}
}
}
}
int main()
{
//freopen("input.txt","r",stdin);
int n;
cin>>n;
for(int i=0;i<n;i++)
{
ans.clear();
temp.clear();
memset(visit,0,sizeof(visit));
num=0;
cin>>b;
dfs(0);
for(int i=0;i<8;i++)
cout<<ans[i]+1;
cout<<endl;
}
return 0;
}
1321 棋盘问题 全排列计数(相当于在八皇后问题中去掉斜线条件,增加了一个可摆放位置条件和一个总棋子个数条件(皇后中棋子个数一定等于行数,这里可能小于行数因而递归出口))
dfs(int 当前行 , int 当前第几个数)表示在当前行放第几个数 与皇后相比多了一个条件参数,因为皇后问题每一行必定放一个数,所以行数等于棋子个数,也就不需要额外判断棋子数,这里棋盘若在当前行没找到符合条件的位置,仍然需要递归到下一行,所以需要额外参数标识
DFS(index+1,dijigeshu+1);//该行(第index行)找到了能放的位置并放入,到下一行
DFS(index+1,dijigeshu); //该行没有符合条件的位置,直接到下一行
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
char a[10][10]; //记录棋盘位置
bool book[10]; //记录一列是否已经放过棋子
int n,k;
int total,m; //total 是放棋子的方案数 ,m是已放入棋盘的棋子数目
void DFS(int index,int dijigeshu)
{
if(dijigeshu==k)
{
total++;
return ;
}
if(index>k) //边界
return ;
for(int j=0; j<n; j++)
//判断条件,与八皇后相比这里多了判断是否为棋盘区的条件
if(book[j]==0 && a[index][j]=='#')
{
book[j]=1;
DFS(index+1,dijigeshu+1);//该行(第index行)找到了能放的位置并放入,到下一行
book[j]=0;
}
DFS(index+1,dijigeshu); //与八皇后的区别,该行没有符合条件的位置,直接到下一
}
int main()
{
int i,j;
//freopen("input.txt","r",stdin);
while(scanf("%d%d",&n,&k)&&n!=-1&&k!=-1) //限制条件
{
total=0;
m=0;
for(i=0; i<n; i++)
scanf("%s",&a[i]);
memset(book,0,sizeof(book));
DFS(0,0);
printf("%d\n",total);
}
return 0;
}
9.4 草丛问题
dfs
1 访问
2 发散
2816 红与黑 草丛内方块个数
dfs(int x ,int y) 表示访问点(x,y)(该点进入函数前未访问)并向可能的方向发散
#include<cstdio>
#include<math.h>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<string>
#include<map>
#include<set>
#include<vector>
#define MAX 21
#define num 32767
#define INF 0x7f7f7f
#define eps 1e-5
using namespace std;
int w,h,qidianx,qidiany;
int X[]={-1,1,0,0};
int Y[]={0,0,-1,1};
char mp[MAX][MAX];
bool visit[MAX][MAX];
bool check(int x ,int y)
{
if(x<0||x>=h||y<0||y>=w) return 0;
if( visit[x][y]||mp[x][y]=='#') return 0;
return 1;
}
void dfs(int x,int y)
{
//check(省略)
//访问
visit[x][y]=1;
//四个方向遍历
for(int i=0;i<4;i++)
{
int newx = x+X[i];
int newy = y+Y[i];
if(check(newx,newy)) dfs(newx,newy);
}
return ;
}
int main()
{
//freopen("input.txt","r",stdin);
while(cin>>w>>h&&w!=0&&h!=0)
{
memset(visit,0,sizeof(visit));
for(int i=0;i<h;i++)
{
for(int j=0;j<w;j++)
{
cin>>mp[i][j];
if(mp[i][j]=='@')
{
qidianx=i;
qidiany=j;
}
}
}
dfs(qidianx,qidiany);
int ans =0;
//只有一个草丛,所有访问过的都是组成草丛的块
for(int i=0;i<h;i++)
for(int j=0;j<w;j++)
if(visit[i][j]) ans++;
cout<<ans<<endl;
return 0;
}
2815 城堡问题 草丛内方块个数+草丛丛数
dfs(int x,int y)表示访问点(x , y)
注意
1 main中的for记录草丛丛数
2 dfs中的循环记录草丛方块数
#include<cstdio>
#include<math.h>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<string>
#include<map>
#define MAX 53
#define num 32767
#define INF 0x7f7f7f
#define eps 1e-5
using namespace std;
int dx,nb;
int room=0,fk,maxfk;
int D[MAX][MAX];
bool visit[MAX][MAX]={0};
//这里四个方向能否走通需要额外判断
void dfs(int i, int j)
{
//检查
if(visit[i][j]) return;
if(i<=0||j<=0||i>nb||j>dx) return ;
//访问
visit[i][j]=1;//必有
fk++;
//向四个方向延申
if((D[i][j]&1)==0) dfs(i,j-1);//刚开始没给&运算加括号,==优先级高于按位与
if((D[i][j]&2)==0) dfs(i-1,j);
if((D[i][j]&4)==0) dfs(i,j+1);
if((D[i][j]&8)==0) dfs(i+1,j);
}
int main()
{
//freopen("input.txt","r",stdin);
cin>>nb>>dx;
for(int i=1;i<=nb;i++)
for(int j=1;j<=dx;j++)
cin>>D[i][j];
memset(visit,sizeof(visit),0);
for(int i=1;i<=nb;i++)
{
for(int j=1;j<=dx;j++)
{
if(!visit[i][j])
{
room++;
fk=0;
dfs(i,j);
maxfk=max(maxfk,fk);
}
}
}
cout<<room<<endl<<maxfk<<endl;
return 0;
}
9.5 迷宫问题
dfs
终点
剪枝
发散
9.5.1 能否走出迷宫(可达问题)
2790 迷宫
dfs(int x,int y ) 表示从(x,y)出发(该点进入函数前已访问)访问可能的发散方向
#include <iostream>
#include<cstdio>
#include<vector>
#include<string>
#include<map>
#include <math.h>
#include<algorithm>
#include <string.h>
#define MAX 105
using namespace std;
int k,n,ha, la, hb, lb,label=1;
char D[MAX][MAX];
bool visit[MAX][MAX];
int X[]={-1,0,0,1};
int Y[]={0,-1,1,0};
bool check(int x,int y)
{
if(visit[x][y]||D[x][y]=='#') return 0;
if(x<0||y<0||x>=n||y>=n) return 0;
return 1;
}
void dfs(int x, int y)
{
//终点
if(x==hb&&y==lb)
{
label = 0;
return ;
}
//剪枝
if(x<0||y<0||x>=n||y>=n) return ;
//发散
for(int i=0;i<4;i++)
{
int newx = x+X[i];
int newy = y+Y[i];
if(check(newx,newy))
{
visit[newx][newy]=1;
dfs(newx,newy);
//visit[newx][newy]=0;
//问是否可达,dfs回溯时不需要记录数组归零。
//如果是求最短路径则需要归零。 回溯时是否归零看具体情况而定
}
}
}
int main()
{
//freopen("input.txt","r",stdin);
cin>>k;
for(int i=0;i<k;i++)
{
cin>>n;
memset(visit,0,sizeof(visit));
for(int m=0;m<n;m++)
for(int mm=0;mm<n;mm++)
cin>>D[m][mm];
cin>>ha>>la>>hb>>lb;
if(D[ha][la]=='#'||D[hb][lb]=='#')
{
cout<<"NO"<<endl;
continue;
}
label=1;
visit[ha][la]=1;
dfs(ha,la);
if(!label) cout<<"YES"<<endl;
else cout<<"NO"<<endl;
}
return 0;
}
9.5.2 走出迷宫的最短路径长度问题
3752 走迷宫
dfs(int x, int y , int step) 表示从(x,y)出发(该点进入函数前已访问)访问可能的发散方向且已走了step步
//输入char map[MAX][MAX]; 定义 bool visit[MAX][MAX];
void dfs(int x,int y,int step)
{
//终点检查
if(x==zhongdianX && y==zhongdianY)
{
//更新最短步数
ans = min(ans,step);
return ;
}
//剪枝
if(!check(x,y)) return ;
//发散访问
for(int i=0;i<4;i++)
{
int newx= x+X[i];
int newy= y+Y[i];
if(check(newx,newy))
{
visit[newx][newy]=1;
dfs(newx,newy,step+1);
visit[newx][newy]=0;
}
}
}
9.5.3 走出迷宫的最短路径问题
4127 迷宫问题
//输入char map[MAX][MAX]; 定义 bool visit[MAX][MAX];
//定义 stack<node> temp,ans; 额外增加一个队列或者栈以保存路径(类似全排列保存遍历中得到的某个排列组合,在出口处更新最佳组合)
void dfs(int x,int y,int step)
{
//终点检查
if(x==zhongdianX && y==zhongdianY)
{
//更新最短步数或者最短路径序列
min_step = min(ans,step);
ans = temp;//保存路径
return ;
}
//剪枝
if(min_step<step) return ;
//发散访问
for(int i=0;i<4;i++)
{
int newx= x+X[i];
int newy= y+Y[i];
if(check(newx,newy))
{
visit[newx][newy]=1;
temp.push(节点);//保存路径
dfs(newx,newy,step+1);
temp.pop();//回复路径
visit[newx][newy]=0;
}
}
}
#include <iostream>
#include <cstring>
#include <cstdio>
#include <map>
#include <cmath>
#include <vector>
#include <stack>
#include<queue>
#include <algorithm>
#define MAX 0x3f7f7f7f
using namespace std;
int m[5][5];
bool visit[5][5]={false};
struct node
{
int x;int y;
}s,e,temp;
long int minstep;
stack<node > q,ans;
int X[]{1,-1,0,0};
int Y[]{0,0,1,-1};
bool check(int x,int y)
{
if(x<0||x>4||y<0||y>4) return 0;
if(visit[x][y]==1||(visit[x][y]==0&&m[x][y]==1)) return 0;
return 1;
}
void dfs(int x,int y,int step)
{
if(x==e.x&&y==e.y)
{
if(step<minstep)
{
minstep=step;
ans=q;
}
return;
}
if(step>minstep) return ;
for(int i=0;i<4;i++)
{
int newx=x+X[i];
int newy=y+Y[i];
if(check(newx,newy))
{
temp.x=newx;
temp.y=newy;
visit[newx][newy]=1;
q.push(temp);
dfs(newx,newy,step+1);
visit[newx][newy]=0;
q.pop();
}
}
}
int main()
{
//freopen("input.txt","r",stdin);
memset(m,0,sizeof(m));
memset(visit,0,sizeof(visit));
s.x=4;s.y=4;
e.x=0;e.y=0;
minstep=MAX;
for(int i=0;i<5;i++)
for(int j=0;j<5;j++)
cin>>m[i][j];
visit[s.x][s.y]=1;
q.push(s);
dfs(s.x,s.y,0);
while(!ans.empty())
{
cout<<"("<<ans.top().x<<", "<<ans.top().y<<")"<<endl;
ans.pop();
}
return 0;
}
9.6 总的操作步数固定的枚举
2787 算24 重要
dfs(int 第几步操作) 表示当前仅进行第几步操作
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<map>
#include<set>
#include<ctime>
using namespace std;
typedef long long ll;
#define INF 0x3f3f3f3f
#define maxn 5
#define eps 1e-8
double a[maxn];
int flag,vis[maxn];//visit表示已被合并
int sign(double x)
{
if(fabs(x)<eps)return 0;
if(x>eps)return 1;
return -1;
}
void dfs(int num)//num表示当前正在进行第几步操作
{
if(flag)return ;
if(num==4) //输入的四个数经过三次运算就能得到一个数
{
//找到最后被保存的那个数
for(int i=1;i<=4;i++)
if(!vis[i]&&sign(a[i]-24.0)==0)flag=1;
return ;
}
//选两个数a[i],a[j]进行操作,结果放到a[i]中
for(int i=1;i<=4;i++)
if(!vis[i])
for(int j=i+1;j<=4;j++)
if(!vis[j])
{
//标记
vis[j]=1;
double x=a[i],y=a[j];
//六个方向dfs
if(sign(y))a[i]=x/y,dfs(num+1);
if(sign(x))a[i]=y/x,dfs(num+1);
a[i]=x-y,dfs(num+1);
a[i]=y-x,dfs(num+1);
a[i]=x+y,dfs(num+1);
a[i]=x*y,dfs(num+1);
//撤销标记
a[i]=x;
vis[j]=0;
}
}
int main()
{
while(1)
{
for(int i=1;i<=4;i++)scanf("%lf",&a[i]);
if(sign(a[1]+a[2]+a[3]+a[4])==0)break;
flag=0;
memset(vis,0,sizeof(vis));
dfs(1);
printf("%s\n",flag?"YES":"NO");
}
return 0;
}