北大培训课动态规划----神奇的口袋(百练2755)

北京大学暑期课《ACM/ICPC竞赛训练》 ppt摘取

什么是动态规划?

●递归到动规的一般转化方法 

递归函数有n个参数,就定义一个n维的数组,数组 的下标是递归函数参数的取值范围,数组元素的值 是递归函数的返回值,这样就可以从边界值开始, 逐步填充数组,相当于计算递归函数值的逆过程。

动规解题的一般思路 

1. 将原问题分解为子问题 

把原问题分解为若干个子问题,子问题和原问题形式相同 或类似,只不过规模变小了。子问题都解决,原问题即解 决(数字三角形例)。 

子问题的解一旦求出就会被保存,所以每个子问题只需求 解一次。 

2. 确定状态 

在用动态规划解题时,我们往往将和子问题相 关的各个变量的一组取值,称之为一个“状 态”。一个“状态”对应于一个或多个子问题, 所谓某个“状态”下的“值”,就是这个“状 态”所对应的子问题的解。 

 所有“状态”的集合,构成问题的“状态空间”。“状态 空间”的大小,与用动态规划解决问题的时间复杂度直接相关。 在数字三角形的例子里,一共有N×(N+1)/2个数字,所以这个 问题的状态空间里一共就有N×(N+1)/2个状态。         

整个问题的时间复杂度是状态数目乘以计算每个状态所需 时间。             

在数字三角形里每个“状态”只需要经过一次,且在每个 状态上作计算所花的时间都是和N无关的常数。

用动态规划解题,经常碰到的情况是,K个整型变量能 构成一个状态(如数字三角形中的行号和列号这两个变量 构成“状态”)。如果这K个整型变量的取值范围分别是 N1, N2, ……Nk,那么,我们就可以用一个K维的数组 array[N1] [N2]……[Nk]来存储各个状态的“值”。这个 “值”未必就是一个整数或浮点数,可能是需要一个结构 才能表示的,那么array就可以是一个结构数组。一个 “状态”下的“值”通常会是一个或多个子问题的解。 

3. 确定一些初始状态(边界状态)的值  
       以“数字三角形”为例,初始状态就是底边数字,值 就是底边数字值。 

4. 确定状态转移方程  
       定义出什么是“状态”,以及在该 “状态”下的“值”后,就要 找出不同的状态之间如何迁移――即如何从一个或多个“值”已知的 “状态”,求出另一个“状态”的“值”(“人人为我”递推型)。状 态的迁移可以用递推公式表示,此递推公式也可被称作“状态转移方 程”。       

能用动规解决的问题的特点 

1) 问题具有最优子结构性质。如果问题的最优解所包含的 子问题的解也是最优的,我们就称该问题具有最优子结 构性质。

 2) 无后效性。当前的若干个状态值一旦确定,则此后过程 的演变就只和这若干个状态的值有关,和之前是采取哪 种手段或经过哪条路径演变到当前的这若干个状态,没 有关系。 

动归的三种形式 

1)记忆递归型    

优点:只经过有用的状态,没有浪费。递推型会查看一些 没用的状态,有浪费    

缺点:可能会因递归层数太深导致爆栈,函数调用带来额 外时间开销。无法使用滚动数组节省空间。总体来说,比递推 型慢。

2) “我为人人”递推型     

没有什么明显的优势,有时比较符合思考的习惯。个别特 殊题目中会比“人人为我”型节省空间。

状态i的值Fi在被更新(不一定是 最终求出)的时候,依据Fi去更 新(不一定是最终求出)和状态i 相关的其他一些状态的值 Fk,Fm,..Fy 

                      Fk 
                      Fm 
   Fi    ->        Fx 
                      Fy 
                      …    

3)“人人为我”递推型动归  

状态i的值Fi由若干个值 已知的状态值Fk,Fm,..Fy 推出,如求和,取最大值 …… 

在选取最优备选状态的值Fm,Fn,…Fy时, 有可能有好的算法或数据结构可以用来显 著降低时间复杂度。 

Fk 
Fm 
Fx      ->     Fi 
Fy 
… 


例题 神奇的口袋(百练2755)

有一个神奇的口袋,总的容积是40,用这个口袋可以变出一 些物品,这些物品的总体积必须是40。

John现在有n(1≤n ≤ 20)个想要得到的物品,每个物品 的体积分别是a1,a2……an。John可以从这些物品中选择一 些,如果选出的物体的总体积是40,那么利用这个神奇的口 袋,John就可以得到这些物品。现在的问题是,John有多少 种不同的选择物品的方式。

输入

输入的第一行是正整数n (1 <= n <= 20),表示不同的物品的 数目。接下来的n行,每行有一个1到40之间的正整数,分别 给出a1,a2……an的值。

输出

输出不同的选择物品的方式的数目。

输入样例 3 20 20 20

输出样例 3


1.枚举的解法:

枚举每个物品是选还是不选,共2^20种情况

2.递归解法

#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
using namespace std;
#define N 100
int a[N],n;
int ways(int n,int w)   //从前n种物品中选择一些,凑成体积w的做法数目 
{
    if(w==0) return 1;
    if(n<=0) return 0;
    return ways(n-1,w)+ways(n-1,w-a[n]);
}
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
    cin>>a[i];
    cout<<ways(n,40)<<endl;
    return 0;
}

动规解法

#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
using namespace std;
#define N 100
int dp[N][50],n,a[N];  //dp[i][j]表示从前i种物品里凑出体积j的方法数 

int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        dp[i][0]=1;
    }
    dp[0][0]=1;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=40;j++)
        {
            dp[i][j]=dp[i-1][j];
            if(j>=a[i])
            dp[i][j]+=dp[i-1][j-a[i]];
        }
    }
    cout<<dp[n][40]<<endl;
    return 0;
}


“我为人人”型递推解法

此问题仅在询问容积40是否可达,40是个很小的 数,可以考虑对值域空间-即对容积的可达性进行动态 规划。
定义一维数组 int sum[41];依次放入物品,计算每次放入物品可达的容积, 并在相应空间设置记录,最后判断sum[40] 是否可达 ,到达了几次。

#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
using namespace std;
#define N 100
int dp[N],n,a;

int main()
{
    cin>>n;
    memset(dp,0,sizeof(dp));
    for(int i=1;i<=n;i++)
    {
        cin>>a;
        for(int j=40;j>=1;j--)   //一定为逆序,否则会重复更新
           if(dp[j]>0&&j+a<=40)
             dp[j+a]+=dp[j];
              //如果j有dp[j]种方式可达,则每种方式加上a就可达 j+a
        dp[a]++;
    }
    cout<<dp[40]<<endl;
    return 0;
}


最长上升子序列的“我为人人”做法:

#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;
#define N 1000
int dp[N],n,a[N];

int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        dp[i]=1;
    }
    for(int i=1;i<=n;i++)
    {
        for(int j=i+1;j<=n;++j) //看看能更新哪些状态的值
        {
            if(a[j]>a[i])
            dp[j]=max(dp[j],dp[i]+1);
        }
    }
    cout<<*max_element(dp+1,dp+n+1)<<endl;
    return 0;
}







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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值