动态规划入门(一)

1. 什么是动态规划

  • 英语: Dynamic programming ,简称DP。因此刷算法的人,一般称 动态规划问题 为 DP问题 。

  • 核心概念:保存 递归时 的结果, 以减少后续解决同样的问题时所花费的时间。

  • 本质:利用已解决问题的答案去解决新问题 。 即用小问题的解法去解大问题。

  • 使用场景:1. 最优子结构性质;2. 无后效性;3. 子问题重叠性质

2. 闫氏dp分析法

有限集合中的最值

DP问题两个阶段:

  • 状态表示
    • 集合:对于集合的定义(核心)
    • 属性:最大值、最小值、数量
  • 状态计算
    • 划分子集

划分子集依据:寻找最后一个不同点

3. 背包问题

/*
1. 01背包:  f[i][j] = max(f[i - 1][j], f[i - 1][j - v] + w);
2. 完全背包: f[i][j] = max(f[i - 1][j], f[i][j - v] + w);
*/
#include<iostream>
using namespace std;
const int N = 1010;

int n, m;
int v[N], W[N];//v: 体积,w: 价值 
//int f[N][N];//只选i件物品不超过j体积的总价值
int f[N];//优化

int main(){
    cin >> n >> m;
    for (int i = 1; i <= n; i ++ ) cin >> v[i] >> w[i];
    
    for (int i = 1; i <= n; i ++)
        //for (int j = 1; j <= m; j++ ) {
        for(int j = v[i]; j<= m; j++ ){
            //f[i][j] = f[i - 1][j];
            //f[j] = f[j];
            //等价于上面式子
            //if (j >= v[i]) 
                //f[i][j] = max(f[i-1][j], f[i][j-v[i]]) + w[i];
            f[j] = max(f[j], f[j-v[i]]) + w[i];
        }
    //cout << f[n][m] << endl; 
    cout << f[m] << endl; 
    return 0;
}

4. 石子合并(区间DP问题)

题目描述
在一个圆形操场的四周摆放 N 堆石子,现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。

试设计出一个算法,计算出将 N 堆石子合并成 1 堆的最小得分和最大得分。

输入格式
数据的第 1 行是正整数 N,表示有 N 堆石子。

第 2 行有 N 个整数,第 i 个整数 ai表示第i堆石子的个数。

输出格式
输出共 2 行,第 1 行为最小得分,第 2 行为最大得分。

输入输出样例
输入 #1
4
4 5 9 4
输出 #1
43
54

/*
	第一步状态表示:
		确定集合与属性,f[i][j]表示将第i堆石子到第j堆石子合并成一堆的方案的集合,属性为最小值
	第二步状态计算:
		f[i][j]可以分为f[i][k]和f[k+1][j]合并,k的范围从i到j
		故f[i][j] = f[i][k] + f[k+1][j] + s[j] - s[i-1] 
		(其中s为前n项合,用s[j] - s[i-1]表示从i到j的部分合)
*/

#include<iostream>
using namespace std;

int N, a[400], s[400], dp[400][400];//放最小 
int f[110][110];//放最大 

int main(){
	cin >> N;
	for(int i=1;i<=N;i++){
		cin >> a[i];
		s[i] = s[i-1] + a[i];
		//dp[i][i] = a[i];
	}
	
	for(int len=2;len<=N;len++){
		for(int i=1;i<=N-len+1;i++){
			int j = i + len - 1;
			dp[i][j] = 1e8;
			for(int k=i;k<j;k++){
				f[i][j] = max(f[i][j], f[i][k] + f[k+1][j] + s[j] - s[i-1]);
				dp[i][j] = min(dp[i][j], dp[i][k] + dp[k+1][j] + s[j] - s[i-1]);
			}
		}
	}
	cout << dp[1][N] << endl;//最小值
	cout << f[1][N] << endl; //最大值
	return 0;
}

5. 传球游戏

【问题描述】
  上体育课的时候,小蛮的老师经常带着同学们一起做游戏。这次,老师带着同学们一起做传球游戏。
  游戏规则是这样的:n个同学站成一个圆圈,其中的一个同学手里拿着一个球,当老师吹哨子时开始传球,每个同学可以把球传给自己左右的两个同学中的一个(左右任意),当老师再次吹哨子时,传球停止,此时,拿着球没传出去的那个同学就是败者,要给大家表演一个节目。
  聪明的小蛮提出一个有趣的问题:有多少种不同的传球方法可以使得从小蛮手里开始传的球,传了m次以后,又回到小蛮手里。两种传球的方法被视作不同的方法,当且仅当这两种方法中,接到球的同学按接球顺序组成的序列是不同的。比如有3个同学1号、2号、3号,并假设小蛮为1号,球传了3次回到小蛮手里的方式有1->2->3->11->3->2->1,共2种。
输入格式
  共一行,有两个用空格隔开的整数n,m(3<=n<=301<=m<=30)。
输出格式
  t共一行,有一个整数,表示符合题意的方法数。
样例输入
3 3
样例输出
2
数据规模和约定
  40%的数据满足:3<=n<=301<=m<=20
  100%的数据满足:3<=n<=301<=m<=30
    
    //思路:根据题目“球传了3次回到小蛮手里的方式有”
    //可以将集合定义为dp[i][j],球传了i次回到j手上的方法总数
    //dp[i][j] = dp[i-1][l] + dp[i-1][r];
    
//AC代码
#include<iostream>
using namespace std;

const int N = 40;
int n, m, dp[N][N];//传i次回到j手上的方法 

int main(){
	cin >> n >> m ;
	dp[0][1] = 1;
	for(int i=1;i<=m;i++){
		for(int j=1;j<=n;j++){
			int l = j - 1, r = j + 1;
			if(j == 1) l = n;
			if(j == n) r = 1;
			dp[i][j] = dp[i-1][l] + dp[i-1][r];
		}
	}
	cout << dp[m][1] << endl;
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值