题目大意:
有一排固定顺序(固定顺序不容忽视)的石子,对于每一块石子,你都可以选择<stron>或者视而不见。对于第i块石子,如果你选择将其点石成金,那么财富值将增加a[i],同时魔法值减少b[i];如果你选择对其视而不见,那么财富值将减少d[i],同时魔法值增加c[i]。求操作完全部石子,所获得的最大收益值。(收益值=魔法值*财富值)</stron>
分析:
标签是搜索嘛,而且就一串石子,如果dfs,时间复杂度也不会很高,毕竟只是一个节点连着一个节点。所以,我们dfs搞起来。
- dfs参数
参数1:
得有一个控制递归结束的标志吧,我们自然需要一个参数代表选到第几个石子了,如果选完了n个石子,那么就return。
参数2,3:
选到第n个石子也不能直接结束啊,因为题目中要求的是收益值,我们得求出操作完所有石子后的收益值,而收益值的获得需要魔法值和财富值,因此,我们需要再加上参数2财富值和参数3魔法值。 - dfs边界
思考参数的时候其实就已经考虑过了,就是当选完第n个石子的时候,就可以return了 - dfs递归的实现
到了难点了。
思考一下,对于第i个石子,你有几种选择?
两种,其一是点石成金,其二是视而不见。因为要搜索到所有的可能,所以我们要对第i个石子进行两种递归,其一是点石成金后进行对第i+1个石子的判断,其二是视而不见后进行对第+1个石子的判断。
不是很懂的话,可以这么理解:
想一下第i+1个石子的来源,一,由 第i个石子变金 转移而来;二,由 第i个石子不变金 转移而来。
这是动态规划的思想,反过来不就是这不就是递归吗?
(自己的理解(突然开窍!!!):向之后寻找转移方程是递归,向之前寻找转移方程是动规)
那么我们就对第i个石子的两种操作,分别递归,传入的参数也根据操作而变动。
我的哔哔赖赖(能理解透彻上面的读者可忽略):
//刚看题的时候头真大,感觉自己肯定又不会(反正看了一天背包了,也没写出一个,已经释怀了)。其实还好。
提一下我刚开始的错误思考,也就是注释掉的部分,之所以不删除,就是为了提醒自己,对比一下区别。
注释部分,我用的是几乎是dfs模板函数,是因为我忽略了这一排石子是固定顺序的,并不是你可以自由挑选,而是只能从1号石子选到n号石子,可以挑选的只是你的操作方式。
其实,从本质上解释一下,注释掉的部分实现的是石子的全排列(为什么说是全排列,可以参考dfs求全排列),之后进行操作,挑选最大的收益值。
也就是说题目的本意只是注释部分的一个子情况,必然wa。
具体讲一下区别:
注释掉的部分可以实现,选完第1个石子,直接跳到选第6个石子(我随便举的,也可以是第2,3……个)。你可能觉得没什么,反正对于每个石子的操作无非是点石成金或者是视而不见。但是同样是操作完了第1个石子,错误代码与正确代码拿到的最大收益值相同(魔法值和财富值也相同),经过下一次操作(错误代码操作第6个石子,正确代码操作第2个石子),由于第6个石子与第2个石子对应的a,b,c,d不相同,所以操作完之后的魔法值,财富值,收益值就不一样了,必然还会影响下一次操作,这也就是问题所在。
希望引以为戒吧!
AC代码:
#include<bits/stdc++.h> #define ll long long using namespace std; int n,vis[20],a[20],b[20],c[20],d[20]; //数组a,b,c,d如题//数组vis是错误代码里的,可忽略 ll ans=0; //存储最终输出的最大收益值 void dfs(int num,ll wealth,ll magic){//第num个石子,wealth财富值,magic魔法值 if(num>n){//判边界 ans=max(ans,wealth*magic);//每次选完n个石子,都要刷新一下最大值 return ;//返回 } /* //错误代码,忽视 for(int i=1;i<=n;i++){ if(vis[i]==0){ vis[i]=1; dfs(num+1,wealth+a[i],magic-b[i]<0?0:magic-b[i]); dfs(num+1,wealth-d[i]<0?0:wealth-d[i],magic+c[i]); vis[i]=0; } } */ dfs(num+1,wealth+a[num],magic-b[num]<0?0:magic-b[num]);//选择点石成金 dfs(num+1,wealth-d[num]<0?0:wealth-d[num],magic+c[num]);//选择视而不见 //注意我这里的三元运算符a-b<0?0:a-b表示“如果a-b小于0,那么返回0,反之返回a-b” } int main(){ cin>>n; for(int i=1;i<=n;i++) cin>>a[i]>>b[i]>>c[i]>>d[i];//正常输入 dfs(1,0,0);//进行dfs cout<<ans; }
闲扯:
第二次写题解,明显比第一次写的好多了,无论是排版还是思路,不得不夸夸自己!!!
写题解的原因很简单(这次自己AC了才有脸写,上次没AC厚着脸写的),就是为了跟我一样苦于看不懂大佬题解的小白能通过简单的方式去理解,同时写题解也是提高自己的过程,就是有点费时间,不过还是值得的。(只放代码的题解都是耍流氓!)
最后的最后,想对天发问:我到底什么时候能成为大佬啊啊啊!!!!!!
//如若有错,望大家指点啊,非常感谢!