训练记录5:基础练习题:dp

动态规划dp

基础动态规划

poj3176

题意:给出一个数字三角形(尖朝上),从顶端开始穿过三角形,向下移动到两个对角相邻的中的一个数字,直到到达底部,求沿途到访数字的总和

思路:从上到下对每个数字求最大值即可,最大值就是取上面两个值的最大值就行了,注意左右两边只能取一个值

#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<vector>
#include<queue>
#include<map>
#include<set>
#include<stack>
#include<iomanip>
using namespace std;
typedef long long ll;
const long long ll_inf=0x3f3f3f3f3f3f3f3f;
const int inf=0x3f3f3f3f;
const double pi=acos(-1.0);
const int Mod=1e9+7;
const int Max=350;
//for(int i=0;i<n;i++)
//cout<<setw(8)<<fixed<<setprecision(3);
//ios::sync_with_stdio(false);

int main(){
	int n;
	cin>>n;
	int a[Max][Max];
	for(int i=0;i<n;i++){
		for(int j=0;j<i+1;j++){
			cin>>a[i][j];
		}
	}
	for(int i=1;i<n;i++){
		for(int j=0;j<i+1;j++){
			if(j==0)
			a[i][j]=a[i-1][j]+a[i][j];
			else if(j==i)
			a[i][j]=a[i-1][j-1]+a[i][j];
			else
			a[i][j]=max(a[i-1][j-1]+a[i][j],a[i-1][j]+a[i][j]);
		}
	}
	int ans=0;
	for(int i=0;i<n;i++){
		ans=max(a[n-1][i],ans);
	}
	cout<<ans;
}

 疯狂写水题找自信


poj2229

题意:任何数都可以分解成2的幂次方的和(1,2,4。。。),给出一个数,问用2的幂次方有多少种表示方法

思路:dp递推,有一下公式:

当n为奇数,有dp[n] = dp[n-1];

当n为偶数,有dp[n] = dp[n-1]+dp[n/2];

证明:对于奇数n的情况,很明显就是偶数n-1的所有凑法+1即可,对于偶数的情况,我没推出来,看了大神的解释,我再重载一下:对于偶数n,假设有n1种含1的凑法和n2种不含1的凑法,很明显n1=dp[n-1],因为这些加1就一定含1,对于n2,不含1的凑发,则由2,4,8等等组成,全部除以2就是dp[n/2]的凑法,因为dp[n/2]的凑法*2一定不含1且刚好凑到n;

#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<vector>
#include<queue>
#include<map>
#include<set>
#include<stack>
#include<iomanip>
using namespace std;
typedef long long ll;
const long long ll_inf=0x3f3f3f3f3f3f3f3f;
const int inf=0x3f3f3f3f;
const double pi=acos(-1.0);
const int Max=1000000+5;
const int Mod=1e9; 
//cout<<setw(8)<<fixed<<setprecision(3);
//ios::sync_with_stdio(false);
int dp[Max];
int main(){
	dp[0] = 0;
	dp[1] = 1;
	dp[2] = 2;
	for (int i=3;i<Max;i++){
		if (i%2==1){
			dp[i] = dp[i-1]%Mod;
		}else
			dp[i]=(dp[i/2]+dp[i-1])%Mod;
	}
	int a;
	scanf("%d",&a);
	printf("%d\n",dp[a]);
	return 0;
}

另外,这还可以用完全背包的思想来做,把1,2,4。。。看成物品,给出的n看作容量,虽然求的是装背包的方法,不过大同小异。感觉这里很有必要把完全背包的递推变形的式子拿出来推一推

用i+1种物品占j重量的最大价值等于用i种物品在背包为j-k*重量的情况下装上k个第i+1物品的价值,然后枚举k看要装多少;当k=0时后面等于 dp[i][j] ;然后把k变k+1,这样前面就变成了第一个式子,然后就可以约去多个式子变成两个式子的最大值了。

稍微变化一下就可以变成用i+1种物品占j重量的方法: 

	memset(dp,0,sizeof(dp));
	dp[0]=1;
	for(i=1;i<=n;i=i<<1){
		for(j=i;j<=n;j++){
			dp[j]+=dp[j-i];
			dp[j]%=Mod;
		}
	}
	cout<<dp[n]<<endl;

这里是直接优化成一维数组了,并且直接用i代替了w[i]


poj2385

题意略

思路:动态规划,设dp[i][j]为变化i次,第j棵树最多能得到的苹果,注意这里的变化i次是一定要变化i次,这样奶牛的位置就是确定的且对后续答案没有影响,状态转移方程就可以写出来了

#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<vector>
#include<queue>
#include<map>
#include<set>
#include<stack>
#include<iomanip>
using namespace std;
typedef long long ll;
const long long ll_inf=0x3f3f3f3f3f3f3f3f;
const int inf=0x3f3f3f3f;
const double pi=acos(-1.0);
const int Max=1000+5;
const int Mod=1e9; 
//cout<<setw(8)<<fixed<<setprecision(3);
//ios::sync_with_stdio(false);
int main(){
	int t,w;
	cin>>t>>w;
	int apple[Max];
	for(int i=1;i<=t;i++){
		cin>>apple[i];
	}
	int dp[31][Max]={0};
	for(int j=1;j<=t;j++){
		if(apple[j]==1)
		dp[0][j]=dp[0][j-1]+1;
		else
		dp[0][j]=dp[0][j-1];
	}
	for(int i=1;i<30;i++){
		dp[i][1]=1;
	}
	for(int i=1;i<=w;i++){//次数
		for(int j=2;j<=t;j++){//第几分钟
			if(apple[j]==i%2+1)
			dp[i][j]=max(dp[i][j-1],dp[i-1][j-1])+1;
			else
			dp[i][j]=max(dp[i][j-1],dp[i-1][j-1]); 
		}
	}
	cout<<dp[w][t]<<'\n';
}

当 当前位置j可以吃到苹果时,变化i次,第j棵树最多能得到的苹果是要么变化要么不变化的第j-1棵树+1,反之当前位置不能吃到苹果的话就不+1


poj3616

题意:有一群奶牛要挤奶,每只奶牛有开始时间,结束时间和挤奶量,并且每只奶牛挤奶后有一段休息时间,这段时间其他奶牛不能挤奶,问最多能挤多少奶

思路:本来想贪心的,但是每段时间有权值,还有重叠,不会贪,考虑到奶牛最多1k只,于是老实dp,先对奶牛以开始时间排序,方便dp时候的循环,设dp[i]为前i只奶牛最大挤奶量,初始值为第i只奶牛的挤奶量(后称val),状态转移方程为dp[i] = max(dp[i] ,dp[j]+i.val), j: 0→i,意思就是第j只奶牛的最大val+第i只奶牛的挤奶量,条件是j.end+r<=i.start,有点背包那味

#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<vector>
#include<queue>
#include<map>
#include<set>
#include<stack>
#include<iomanip>
using namespace std;
typedef long long ll;
const long long ll_inf=0x3f3f3f3f3f3f3f3f;
const int inf=0x3f3f3f3f;
const double pi=acos(-1.0);
const int Max=1000+5;
const int Mod=1e9;
#define _for(i,n) for(int i=0;i<n;++i)
#define _rep(i,n) for(int i=1;i<=n;++i)
//cout<<setw(8)<<fixed<<setprecision(3);
//ios::sync_with_stdio(false);
int N,M,R;
int dp[Max]; 
struct node{
	int sta,end,val;
	bool operator < (const node &r) const{
        return sta<r.sta;
    }
}cow[Max];
int main(){
	cin>>N>>M>>R;
	_for(i,M){
		cin>>cow[i].sta>>cow[i].end>>cow[i].val;
	}
	int ans=0;
	sort(cow,cow+M);//开始时间排序 
	_for(i,M){
		dp[i]=cow[i].val;
		_for(j,i){
			if(cow[i].sta>=cow[j].end+R)
			dp[i]=max(dp[i],dp[j]+cow[i].val);
		}
		ans=max(ans,dp[i]);
	}
	cout<<ans<<'\n';
	return 0;
}

poj3280 

题意:给出一个字符串,再给出一些字母的删去,添加所耗费的值,问把这个字符串变成回文串需要的最小花费是多少(字符串仅由给出的字母组成,可以在任何位置随意添加或删除)

思路:给出下列递推式子:dp[i][j]表示第i个字母到第j个字母变成回文串最小的花费

设给出字符串s,

当s[i]==s[j],则dp[i][j] = dp[i+1][j-1];

否则,dp[i][j] = min(dp[i+1][j] + s[i].min , dp[i][j-1] + s[j].min) ; 其中s[i].min指s[i]这个字母的删去,添加两者耗费的最小值

 第一个式子很好理解,当左右两头相等,则已经是回文串,耗费与去掉左右两头的字符串变成回文串的花费相同

第二个式子比较难,首先对于串i到j,要得到回文串就要对左边或右边的字母进行删去或添加(在另外一头添加),然后加上相应的值。考虑串是从小到大进行dp的,在进行状态转移的时候只考虑小串变成回文串的花费,并不考虑小串的构成,比如长度的变化也不用管,所以对于删除和添加的操作其实无关后续,因此选择花费最小的操作即可

其他就没啥了,dp数组初始化0即可,题目有提到长度为0的字符串视为回文串

#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<vector>
#include<queue>
#include<map>
#include<set>
#include<stack>
#include<iomanip>
using namespace std;
typedef long long ll;
const long long ll_inf=0x3f3f3f3f3f3f3f3f;
const int inf=0x3f3f3f3f;
const double pi=acos(-1.0);
const int Max=2000+5;
const int Mod=1e9;
#define _for(i,n) for(int i=0;i<n;++i)
#define _rep(i,n) for(int i=1;i<=n;++i)
//cout<<setw(8)<<fixed<<setprecision(3);
//ios::sync_with_stdio(false);
int dp[Max][Max]={0};
int n,m;
string s;
int a[Max];
int w[30];
int main(){
	cin>>n>>m;
	cin>>s;
	_for(i,n){
		a[i]=s[i]-'a';
	}
	int x,y;
	char ch;
	_for(i,n){
		cin>>ch>>x>>y;
		w[ch-'a']=min(x,y);
	}
	sort(al,al+n);
	for(int i=m-1;i>=0;i--){
		for(int j=i+1;j<m;j++){
			if(a[i]==a[j])
			dp[i][j]=dp[i+1][j-1];
			else
			dp[i][j]=min(dp[i+1][j]+w[a[i]],dp[i][j-1]+w[a[j]]);
		}
	}
	cout<<dp[0][m-1]<<'\n';
}

优化递推关系式dp

poj1742

先放一放,dp做吐了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值