Fork me on GitHub
返回顶部
跳到底部

选与不选之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;
} 
posted @ 2018-12-08 12:08  sqmax  阅读(380)  评论(0编辑  收藏  举报