动态规划、贪心、回溯剪枝学习

7-1 凑零钱

题意:给定一定的零钱,看能否凑数一个数字来。

题解:dfs+剪枝,本题只需要轻轻剪一下就够了,深搜的时候判断一下剩下的零钱总数够不够现在的总金额即可。

#include <iostream>
#include <algorithm>
#include <stack>
using namespace std;
stack<int>st;
int a[10005];
int n,m;
bool f(int num,int sum,int s) {//num当前时第几件物品,sum当前需要的剩下的总金额,s当前剩下的总金额
	if(sum==0) {
		return true;
	}
	if(num>=n||s<sum) {//剪枝,如果剩下的零钱数不够,则不需要继续了
		return false;
	}
	s-=a[num];
	if(sum>=a[num]) {//如果该零钱还可以放得下
		sum-=a[num];
		if(f(num+1,sum,s)) {
			st.push(a[num]);
			return true;
		} else {
			sum+=a[num];
		}
	}else{
		return false;
	} 
	if(f(num+1,sum,s)){//如果不放这件零钱
		return true;
	}
	return false;
}
int main() {
	scanf("%d%d",&n,&m);
	int s=0;
	for(int i=0; i<n; i++) {
		scanf("%d",&a[i]);
		s+=a[i];//计算总价值
	}
	sort(a,a+n);
	if(f(0,m,s)) {
		while(!st.empty()) {
			cout<<st.top();
			st.pop();
			if(!st.empty()){
				cout<<" ";	
			}
		}
		cout<<endl;
	} else {
		cout<<"No Solution"<<'\n';
	}
}

7-2 拼题A打卡奖励

题意:给定一定的卷子,做每张卷子需要一定的时间,做完之后会给一定的金币,求在一定时间内获得的最大金币数。

题解:01背包,但是因为 时间的数量级很大,所以会超时,但是金币的数量级很小,所以可以调整01背包,改为总价值为x时,消耗的最小时间。最后遍历所有价值,看价值最大能达到多少,使得时间最小不超过m。

状态标识:f[i],总价值为i的最小时间。

初始化:因为求最小值,所以一开始初始化为正无穷,但是要注意,当金额为0时,消耗的时间也为0,所以f[0]=0。

状态转移:从前面枚举价值j,从大到小循环,最小到w[i]
f [ j ] = m i n ( f [ j ] , f [ j − w [ i ] ] + v [ i ] ) f[j]=min(f[j],f[j-w[i]]+v[i]) f[j]=min(f[j],f[jw[i]]+v[i])

#include <iostream>
#include <cstring>
using namespace std;
int main(){
	int n,m;
	cin>>n>>m;
	int w[1005];
	int v[1005];
	const int mx=1e6+10;
	int dp[mx];
	memset(dp,99,sizeof dp);//因为求最小值,所以初始化为最大值
	dp[0]=0;//金额为0,花费时间也为0
	long long sum=0;
	for(int i=1;i<=n;i++){
		cin>>w[i];
	}
	for(int i=1;i<=n;i++){
		cin>>v[i];
		sum+=v[i];
	}
	for(int i=1;i<=n;i++){
		for(long long j=sum;j>=v[i];j--){
			dp[j]=min(dp[j],dp[j-v[i]]+w[i]);//求 在一定金额的前提下,花费的最小时间
		}
	}
	for(long long j=sum;j>=0;j--){
		if(dp[j]<=m){
			cout<<j<<endl;
			break;
		}
	}
}

7-3 球队“食物链”

题意:n支球队踢球赛,分主客场,每一场有输、赢、平三种结果,求一个环,前一个要赢过后一个

题解:dfs+剪枝,dfs所有情况,在dfs的时候需要注意,若剩下的球队都没有赢过第一支球队,则没必要深搜了,因为最后肯定要有一支球队赢过第一支球队。注意,如果该球队在客场赢过也算赢过

#include <bits/stdc++.h>
using namespace std;
class node {
public:
	map<int,bool>mp;
	int num;	
};
int n;
stack<int>st;
node no[40];
bool dfs(int num,int ceng,bool fi[],int be){//当前是第num支球队,是环中的第几支球队,fi记录,已经有哪些球队被访问过来,be记录环是从哪里开始的
	if(ceng==n){
		if(no[num].mp.find(be)!=no[num].mp.end()){
			st.push(num);
			return true;
		}else{
			return false;
		}
	}
	bool f=0;
	for(int i=1;i<=n;i++){//查看剩下的球队有没有赢过初始球队的,用于剪枝
		if(fi[i]==0&&no[i].mp.find(be)!=no[i].mp.end()){
			f=1;
		}
	}
	if(f==0){
		return false;
	}
	map<int,bool>::iterator it;
	for(it=no[num].mp.begin();it!=no[num].mp.end();it++){//深搜,该球队所英国的球队
		int nx=it->first;
		if(fi[nx]==1||no[num].mp[nx]!=1){//确保该球队没有被访问过
			continue;
		}
		fi[nx]=1;
		if(dfs(nx,ceng+1,fi,be)){
			st.push(num);
			return true;
		}
		fi[nx]=0;
	}
	return false;
} 
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		string str;
		cin>>str;
		no[i].num=i;
		for(int j=0;j<n;j++){
			if(str[j]=='W'){
				no[i].mp[j+1]=1;//球队i赢过球队j+1
			}
			if(str[j]=='L'){
				no[j+1].mp[i]=1;//球队j+1赢过球队i
			}
		}
	}
	bool f=0;
	for(int i=1;i<=n;i++){
		bool fi[40];
		memset(fi,0,sizeof(fi));
		fi[i]=1;
		if(dfs(i,1,fi,i)){
			f=1;
			break;
		}
	}
	if(!f){
		cout<<"No Solution"<<'\n';
	}else{
		while(!st.empty()){
			cout<<st.top();
			st.pop();
			if(!st.empty()){
				cout<<" ";
			}else{
				cout<<endl;
				break;
			}
		}
	}
}

7-5 至多删三个字符

题意:给一个字符串,可以删0,1,2,3个字符,问最多能得到多少个不一样的字符

题解:动态规划+删重,每个字符都可以选择删或者不删,dp[i][j]表示前i个字符,删j个字符,能得到多少个字符串(里面有重复),若j=0,则dp[i][0]=dp[i-1][0],若j!=0,则dp[i][j]=dp[i-1][j]+dp[i-1][j-1],然后删重,看前面在当前j范围内,有没有和当前字符相同的,若有,则说明需要删重,dp[i][j]-=dp[k-1][j-(i-k)],这里k为与i相同的字符下标,j-(i-k)就是需要把两个字符之间的字符都删掉,这样,两个字符串就完全一样了。

#include<iostream>
#include<string>
#include<cstring>
#include<vector>
#include<set>
#include<cmath>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int mx=1000000+10;
ll a[mx][5];//必须为全局变量,不然会段错误,没理解
int main() {
	string str;
	cin>>str;
	string s=" ";
	str=s+str;
	a[0][0]=1;
	for(int i=1; i<str.size(); i++) {
		for(int j=0; j<=3; j++) {
			a[i][j]=a[i-1][j];
			if(j!=0) {
				a[i][j]+=a[i-1][j-1];//前i个字符删j个,结果取决于前i-1个字符删j-1个,或者前i-1个删j个
			}
			for(int k=i-1; k>=1&&i-k<=j; k--) {
				if(str[i]==str[k]) {
					a[i][j]-=a[k-1][j-(i-k)];//去重
					break;//一次就好
				}
			}
		}
	}
	const int t=str.size()-1;
	cout<<a[t][0]+a[t][1]+a[t][2]+a[t][3]<<endl;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值