选与不选之DFS
有这么一类问题:枚举从N个整数中选择K个来满足某种条件。我们可以在使用深度优先时,对每一个元素都可以有选与不选两种方案(有的问题还可以多次选)。
学到这招后,我会解了许多题,下面两个题我一开始就是用这种方法解得。但可惜都不是最优解。
这种方法因为每个元素都有两个选择,时间复杂度很高,下面举例进行说明。
例1: 一 获得特定数量硬币问题
小易准备去魔法王国采购魔法神器,购买魔法神器需要使用魔法币,但是小易现在一枚魔法币都没有,但是小易有两台魔法机器可以通过投入x(x可以为0)个魔法币产生更多的魔法币。
魔法机器1:如果投入x个魔法币,魔法机器会将其变为2x+1个魔法币
魔法机器2:如果投入x个魔法币,魔法机器会将其变为2x+2个魔法币
小易采购魔法神器总共需要n个魔法币,所以小易只能通过两台魔法机器产生恰好n个魔法币,小易需要你帮他设计一个投入方案使他最后恰好拥有n个魔法币。
输入描述: 输入包括一行,包括一个正整数n(1 ≤ n ≤ 10^9),表示小易需要的魔法币数量。
输出描述: 输出一个字符串,每个字符表示该次小易选取投入的魔法机器。其中只包含字符'1'和'2'。
输入例子1: 10
输出例子1: 122
因为每次都有两种选择,投1号箱或2号箱,我的思路如下:
#include<cstdio>
#include<vector>
using namespace std;
vector<int> ans,temp;
int n;
void getMagicCoin(int sum){
if(sum>n) return;
else if(sum==n){
ans=temp;
}
temp.push_back(1);
getMagicCoin(2*sum+1);
temp.pop_back();
temp.push_back(2);
getMagicCoin(2*sum+2);
temp.pop_back();
}
int main(){
int sum=0;
scanf("%d",&n);
getMagicCoin(sum);
for(int i=0;i<ans.size();i++){
printf("%d",ans[i]);
}
return 0;
}
没问题,但是这种算法很暴力,很可能超时。
分析一下,可以得到如下的方案,就没有反复地递归,很好。
#include<iostream>
#include<string>
#include<algorithm>
using namespace std;
int main(){
int n;
cin>>n;
string ans;
while(n>=1){
if(n%2==0){
n=(n-2)/2;
ans+='2';
}else{
n=(n-1)/2;
ans+='1';
}
}
reverse(ans.begin(),ans.end());
cout<<ans<<endl;
return 0;
}
例2:PAT甲级 1045 Favorite Color Stripe (30 分)
我又是用选与不选和DFS做的,虽然还做了剪枝,但还是有两个测试点超时,如下:
//此方法有两个测试点超时
#include<iostream>
#include<vector>
using namespace std;
const int maxn=10010;
const int maxm=210;
int tripe[maxn];
int order[maxm];
bool like[maxm];
vector<int> temp,ans;
int cnt,maxL=0;
//idx:将要收集的颜色
//lastLikeLevel:上一个收集的颜色的喜欢程度
void DFS(int idx,int lastLikeLevel){
if(idx==cnt){
if(temp.size()>maxL){
maxL=temp.size();
ans=temp;
}
return;
}
if(temp.size()+cnt-idx<maxL){//剪枝
return;
}
if(order[tripe[idx]]>=lastLikeLevel){
temp.push_back(tripe[idx]);
DFS(idx+1,order[tripe[idx]]);
temp.pop_back();
DFS(idx+1,lastLikeLevel);
// temp.pop_back();
}else{
DFS(idx+1,lastLikeLevel);
}
}
int main(){
int n,m,l;
cin>>n>>m;
for(int i=1;i<=m;i++){
int a;
cin>>a;
order[a]=i;
like[a]=true;
}
cin>>l;
for(int i=0;i<l;i++){
int a;
cin>>a;
if(like[a]){
tripe[cnt++]=a;
}
}
DFS(0,-1);
cout<<maxL<<endl;
// for(int i=0;i<ans.size();i++){
// cout<<ans[i]<<" ";
// }
return 0;
}
其实这道题可以用动态规划来做,是要求【最长不下降子序列】,如下:
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=10010;
const int maxm=210;
int ht[maxm];
int tripe[maxn],dp[maxn];//dp[i]表示以i结尾的字符的最大子串长度
int main(){
int n,m,l,x;
cin>>n>>m;
fill(ht,ht+maxm,-1);//刚开始写的是ht+m,一个测试点错误,好久没看到,一定要细心
for(int i=0;i<m;i++){
cin>>x;
ht[x]=i;
}
cin>>l;
int cnt=0;
for(int i=0;i<l;i++){
cin>>x;
if(ht[x]>=0){
tripe[cnt++]=ht[x];
}
}
int ans=-1;
for(int i=0;i<cnt;i++){
dp[i]=1;
for(int j=0;j<i;j++){
if(tripe[j]<=tripe[i]&&dp[i]<dp[j]+1){
dp[i]=dp[j]+1;
}
}
ans=max(dp[i],ans);
}
cout<<ans<<endl;
return 0;
}
例三:PTA天梯赛 L3-001 凑零钱 (30 分)
//最后一个测试点超时
//求助
#include<algorithm>
#include<iostream>
#include<vector>
using namespace std;
const int maxn=10010;
int a[maxn];
vector<int> ans,temp;
int n,m;
bool flg=false;
void func(int idx,int sum){
if(sum>m||idx>n||flg==true){
return;
}else if(sum==m){
ans=temp;
flg=true;
return;
}
temp.push_back(a[idx]);
func(idx+1,sum+a[idx]);
temp.pop_back();
func(idx+1,sum);
}
int main(){
cin>>n>>m;
for(int i=0;i<n;i++){
cin>>a[i];
}
sort(a,a+n);
func(0,0);
if(flg){
for(int i=0;i<ans.size();i++){
cout<<ans[i];
if(i<ans.size()-1) cout<<" ";
else cout<<"\n";
}
}else{
cout<<"No Solution\n";
}
return 0;
}