递归
- 定义递归函数为dfs(int a, int x, int y, int z),a代表当前面值,x,y,z 分别对应1块,2块,3块的数量。
- 先找到递归函数出口,如下图所示,当a的值为负值时,不必递归下去,以及当a等于0时,表示已经找换完钱了,所以也可以停止递归下去。
- 接着找递归体,如下图所示,有三个分支,分别是a-1, a-2, a-5,将原问题对a的求解,递归为a-1, a-2, a-5三个子问题的求解,三个子问题又会通过递归求解。
代码如下:
#include <stdio.h>
int count = 0;
int dp[100][100][100];
void dfs(int a, int x, int y, int z){
if(a < 0) return;
if(a == 0 && dp[x][y][z] != 1) {
dp[x][y][z] = 1;
count++;
return;
}
printf("(%d,%d,%d,%d)\n",a,x,y,z);
dfs(a-1, x+1, y, z);
dfs(a-2, x, y+1, z);
dfs(a-5, x, y, z+1);
}
int main()
{
int price;
printf("请输入面额: ");
scanf("%d",&price);
dfs(price-8,1,1,1);
printf("the count is %d",count);
return 0;
}
- 变量count表示换法的数量
- 三维数组dp[x][y][z]用来表示换钱方案的状态,x,y,z 分别对应1块,2块,3块的数量,比如dp[1][1][1]表示一张1块,一张2块,一张3块。由于数组是在全局定义的,因此数组里面存放的都是0, 值为0的时候表示(x,y,z)这种方案没有被采取,值为1表示采取该方案,count值加一。举个例子:10块钱有两种换法,一是(3,1,1),二是(1,2,1),因此在递归的过程中会对数组dp[3][1][1],dp[1][2][1]都赋值1,表示采取这种方案,这么做的目的是避免采取重复的方案
- 在main函数中调用dfs方法,由于题目要求每种面值都要有,所以我这里就直接传入参数(price-8, 1, 1, 1)
但是由于重复的路径过多,面值达到 34 左右时间就爆了,因此需要剪枝 \textcolor{red}{但是由于重复的路径过多,面值达到34左右时间就爆了,因此需要剪枝} 但是由于重复的路径过多,面值达到34左右时间就爆了,因此需要剪枝
剪枝
实际上就是在递归调用前,在dp数组中记录当前的状态,由于当前的子问题在其他路径可能会重复,相同的子问题的解决方案都是相同的(都是通过递归三条分支寻找结果,父问题相同,递归三条分支形成的三个子问题也是相同的),所以当其他路径递归时,遇到了之前已经遇到过的问题,直接return就行,不需要再递归了,可以减少很多重复的路径
#include <stdio.h>
int count = 0;
int dp[100][100][100];
void dfs(int a, int x, int y, int z){
if(a < 0) return;
if(dp[x][y][z] == 1) return; //以前遇到过的情况直接回退
if(a == 0 && dp[x][y][z] != 1) {
dp[x][y][z] = 1;
count++;
return;
}
printf("(%d,%d,%d,%d)\n",a,x,y,z);
int index = 1;
while(index <= 3){
//在递归调用前,在dp数组中记录当前的状态
if(index == 1) { dp[x][y][z] = 1; dfs(a-1, x+1, y, z);}
else if(index == 2) { dp[x][y][z] = 1; dfs(a-2, x, y+1, z);}
else if(index == 3) { dp[x][y][z] = 1; dfs(a-5, x, y, z+1);}
index++;
}
}
int main()
{
int price;
printf("请输入面额: ");
scanf("%d",&price);
dfs(price-8,1,1,1);
printf("the count is %d",count);
return 0;
}
while循环那里水平有限,只能写成那样了
两层for循环
实际上这道题两层for循环就能搞定,以下是链接
for循环解法