蓝桥杯dfs--买书反思与总结+优化思想

题目描述:

买书

蒜头君去书店买书,他有m元钱,书店里面有n本书,每本书的价格为pi​元。蒜头君很爱学习,想把身上钱都用来买书,并且刚好买k本书。请帮蒜头君计算他是否能刚好用m元买k本书。

Input
第一行输入3个整数 m(1≤m≤100000000),n(1≤n≤30),k(1≤k≤min(8,n))

接下来一行输入n个整数,表示每本书的价格 pi(1≤pi≤100000000)。

Output
如果蒜头君能刚好用m元买k本书,输入一行"Yes", 否则输出"No"。

Sample Input 1

10 4 4
1 2 3 4
Sample Output 1

Yes
Sample Input 2

10 4 3
1 2 3 4
Sample Output 2

No

我提交的代码:

#include"iostream"
using namespace std;
int m, n, k,flag = 0,count = 0,f = 0,a[35],vis[35]={0};
// 当前个数  当前的总价 
void dfs(int x,int y){
	if(x == k && y == m){
		flag = 1;
		count ++;
		return ;
	}
	if(count == 1) return;
	if(x > k || y > m) return ;
	
	for(int i = x;i < n;i ++){
		f = 1;
		for(int j = 0;j < x;j ++){
			if(vis[j] == a[i]){
				f = 0;
			}
		}
		if(f){
			vis[x] = a[i];
			dfs(x+1,y+a[i]);
			vis[x] = 0;
		}
	}
}
int main(){
	cin >> m >> n >> k;
	
	for(int i = 0;i < n;i ++){
		cin >> a[i];
	}
	
	dfs(0,0);
	
	if(flag){
		cout << "Yes";
	}else{
		cout << "No";
	}
	return 0;
}

这道题尽管通过了,但是我发现我的思维是有问题的,好几个地方都有瑕疵:
1.我的dfs中的x是表示的当前存入的个数,然而这里我却一直用它当作下标来使用,这里个数与下标的混淆导致思路很乱,假设这个i=0,到是还好理解一些,就是始终遍历所有的,从里面选(只不过改为0,是超时的),
在这里插入图片描述
然而我这里整了一个x后,尽管减少了一些执行次数,但是理解起来很费劲,因为x毕竟表示的是个数,而且也不太合乎逻辑。(其实我想表达的意思是,之前选过的就不会在去选他了,而是在他的下一个位置继续开始,但是显然我的这种做法是没有达到目的的)

2.另外我的代码其实是将所有的符合条件的可能都找了一遍(尽管说在1.中让i=x,可以减少一些情况的发生,但是还是很多,我要做的其实就是只要让他找到一次就可以了,其余的找到了也没用,只会降低时间效率),我这里整了一个count来记录满足情况的个数(我的代码效率很低,比如说如果钱数为10,个数为4,那么1 2 3 4符合,则1 3 2 4也符合。。。这样就有很多,我整了一个count就是想让它记录一次后,就不再记录后面的了),这里整的勉强,尽管说思路是对的,但是不够好

3.对于dfs()中的形参的选择,一定要考虑周全,考虑得当,否则一开始就将自己引入一个麻烦的境地,有时候甚至可以有减少的思想,不一定用累加的思想去实现(就是说,计数的可以是n–,依次递减的,而不是n++,每递归一次加一的)

革命尚未成功,同志仍需努力!!!

在学长的指导下,我的代码有了很多方面的优化,都是值得我去学习和借鉴的:

#include<bits/stdc++.h>
using namespace std;

int money , n , K; // k = 8
int A[35] , M;
bool succ;

void dfs(int pos , int cont , int tot){
	if(tot > money)	return;
	if(cont == K){
		if(tot == money)
			succ = true;
		return;
	}
	int rest = K - cont;
	for(int i = pos; i <= M - rest; ++i){
		dfs(i + 1 , cont + 1 , tot + A[i]);
		if(succ)
			return;
	} 
}

int main(){
	cin >> money >> n >> K;
	M = 0;
	for(int i = 0; i < n; ++i){
		int t;	cin >> t;
		if(t <= money)
			A[M ++] = t;
	}
	sort(A , A + M);
	reverse(A , A + M);
	succ = false;
	dfs(0 , 0 , 0);
	if(succ)	puts("Yes");
	else	puts("No");
	return 0;
}

学习与借鉴:
1.变量的命名很规范,我的显得很随意
2.优化一:对于那些大于money的值,直接忽略了,并且对A数组(即存书价的数组进行了从大到小的排序),这里的从大到小排列很精妙,这能很大的减少dfs的次数,比如:
先看一下从小到大的:

在这里插入图片描述

从大到小的:

在这里插入图片描述
显然这样要节省很多比较次数
3.通过2.可以看出,每次dfs都是只能在它已走过的数值的右边再去选则,不会选之前选过的,也就是对应我之前的代码,在dfs方法中的循环中,没有让i=0,而是等于的当前的下标pos,之后再去向后dfs
在这里插入图片描述
效果就相当于上面那个从小到大的排序的图一样的顺序
4.还有一个地方就是有些分支,可以减支,比如说按照3.中说的这种思想,就是不会再去选之前的了,这样还以那个图为例,最后的分支只有两个了,这就可以直接排除,从而减少了结点数提高了效率。
在这里插入图片描述
举个例子还是 1 1 1 9 9 假设买3本,使用dfs遍历,开始以第一个1为第一层,当第二层是倒数第二个数9的时候,第三层还可以选倒数第一个数9,但是当第二层是倒数第一个9的时候,很显然他的后面已经没有可供选择的数了。这样这种情况直接被排除掉。

再比如 1 2 3 4 5 6 7 8 9,在不考虑钱的情况下再去理解,如果我需要凑4个数,那么如果我的第二个数选7,则第三个数只能选8,最后一个数只能选9,如果要是第二个数选8了,显然它后面数怎么凑也凑不够4个数了,所以就直接舍去,其实对应到每一层,都有不够数的,不够数的就直接排出了

在这里插入图片描述
rest = 要买的个数 - 当前个数(就是还没买的个数), i <= 数组总个数 - rest


学习使人痛苦,不学习更使人痛苦–舍友名言

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Rabbit Coder

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值