基础算法——递推与dp

  讲完递推之后,模拟二分队列等垃圾算法我就不讲了,因为实在太简单,一学就会,等到讲到其他算法要用到的时候一并讲。那么先不废话,直接上题。

题目描述:
RFdragon家有很多兔子,兔子都比较好斗,因此RFdragon希望他们离得越远越好。
RFdragon有n只兔子和m个笼子,所有笼子均位于同一数轴上且不会移动,每个笼子只能住一只兔子。RFdragon希望将所有兔子装进合理的笼子,使得离得最近的两只兔子之间的距离最远,也就是说,希望这两只兔子所在的笼子的坐标差最大。请你帮RFdragon求出这个最大值。

输入描述:
第一行两个正整数n和m,中间用空格隔开。
第二行n个正整数ai,表示所有笼子的坐标。

输出描述:
一行一个正整数,表示离得最近的两只兔子所在笼子的坐标差的最大值。

输入样例:
3 5
2 3 5 7 11

输出样例:
4

其他说明:
1<n<m<10000,a1<a2<a3<……<am,所有数据均在int范围内。

   这次我们来讲一讲倒数第二个基础算法随笔——递推dp递推是什么?想一下著名的斐波那契数列,12358那个,你一定知道有以下规律:f(1)=1,f(2)=2,f(n)=f(n-2)+f(n-1)(n>2,n∈N*)。这就是斐波那契数列递推公式。所谓递推,就是根据一个数组已知的项推出未知的项。我们直接来看一道题:

题目描述:
RFdragon的教室在4层,每天上下楼梯时他很烦。
RFdragon走到教室总共要走n级台阶,由于腿短,RFdragon每次能上1~2级台阶。请你变成帮他求出总共有多少种方法走到第n级台阶。

输入描述:
一个正整数n,表示有n级台阶。

输出描述:
一个数,表示走到第n级台阶的方案数。

输入样例:
5 2

输出样例:
8

其他说明:
n<10000

  根据题意我们知道,如果我想走到第i级台阶,我只能从第i-2或i-1级台阶走上来,也就是说,走到第i级台阶的方案数等于走到第i-2级台阶和i-1级台阶的方案数之和。这不正与斐波那契数列相吻合吗?代码如下:

#include<cstdio>
int n,a[10010];
int main()
{
    scanf("%d",&n);
    a[1]=1,a[2]=2;
    for(int i=3;i<=n;i++)
        a[i]=a[i-2]+a[i-1];
    printf("%d",a[n]);
    return 0;
} 

  这就是递推的精髓,是不是很简单?

  最后,我们来说一说最后一个基础算法,dp,也就是动态规划

题目描述:
RFdragon要去春游(现在是夏天啊!),他想带很多很多东西。
已知RFdragon有n样想带的东西,但由于背包有限,只能装其中的一部分物品。每样物品有自己的体积和价值,价值越大的物品RFdragon越想带。假设RFdragon的背包容积为v,请你帮他求出能够携带物品的最大价值和。

输入描述:
第一行两个正整数n、v,中间用一个空格隔开,表示RFdragon想带的物品的数量和背包容积。
接下来n行,每行两个正整数wi和ci,分别表示一件物品的体积和价值。

输出描述:
一个正整数,表示携带物品的最大价值和。

输入样例:
3 10
5 9
5 9
6 15

输出样例:
18

其他说明:
n,v<1000

  看到这道题,你想了想,一定是先装价值与体积比值最大的。你又看了看样例,放弃了这个想法。冥思苦想之后,你被迫承认,这道题用贪心是做不出来的。这时,你就会用到伟大的动态规划动态规划递推的思想其实差不多,我们来仔细看一看。我们先将复杂的问题简单化,假设只有一个物品,你能携带物品的最大价值和是什么呢?如果背包能装下这个物品,那么最大价值和就是这个物品的价值;否则就是0。那么如果有两个物品呢?其实第二个物品和第一个物品一样,有装和不装两种可能性。假设要装,那么就必须将背包腾出等同于这个物品体积的空间,我们先假设这个物品体积为w,价值为c,那么我们只有在剩余容积大于w时才能装下这个物品,这时,最大价值和就等于背包容积等于v-w是的最大价值和加上c;假设不装,那么最大价值和就等于只能装第一个物品时的最大价值。我们首先定义一个二维数组f,f[i][j]表示只能装前i个物品,背包体积为j时的最大价值和。那么我们可以得出一个公式:f[i][j]=max(f[i-1][j-w]+c[i],f[i-1][j]);。在动态规划中,这样的公式被称为动态转移方程。得到了动态转移方程,这道题就迎刃而解。完整代码如下:

#include<algorithm>
#include<cstdio>
using namespace std;
int n,v,w[1010],c[1010],f[1010][1010];
int main()
{
    scanf("%d %d",&n,&v);
    for(int i=1;i<=n;i++)
        scanf("%d %d",&w[i],&c[i]);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=v;i++)
            f[i][j]=max(f[i-1][j-w[i]]+c[i],f[i-1][j]);
    printf("%d",f[n][v]);
    return 0;
} 

  你会发现,f数组有些大,很可能会超限。这时,我们需要将动态规划过程进行降维,使f数组变成一维。你会发现,f数组的第一维在算法中并没有什么实际作用,因此可以被省略,如下:

#include<algorithm>
#include<cstdio>
using namespace std;
int n,v,w[1010],c[1010],f[1010];
int main()
{
    scanf("%d %d",&n,&v);
    for(int i=1;i<=n;i++)
        scanf("%d %d",&w[i],&c[i]);
    for(int i=1;i<=n;i++)
        for(int j=v;j>=1;i--)
            f[j]=max(f[j-w[i]]+c[i],f[j]);
    printf("%d",f[v]);
    return 0;
} 

  你可能会发现,我将内层循环倒着写。这是因为,如果正着写,每个物品就可以被多次使用。仔细想一想,你就明白为什么了。

  动态规划博大精深,背包问题是他最浅显的一个分支。此外,还有区间dp状压dp线段dp树状dp等等……

  这就是递推dp的用法,你学会了吗?

//答案代码 
#include<cstdio>
int n,m,a[10010];
bool check(int x)
{
    int num=n-1,cur=a[1];
    for(int i=1;i<=m;i++)
        if(a[i]-cur>=x)
        {
            cur=a[i],num--;
            if(num==0)
                return 1;
        }
    return 0;
}
int main()
{
    int head=1,tail,mid;
    scanf("%d %d",&n,&m);
    for(int i=1;i<=m;i++)
        scanf("%d",&a[i]);
    tail=a[m];
    while(head<tail)
    {
        if(check(mid))
            head=mid;
        else
            tail=mid-1;
    } 
    printf("%d",head);
    return 0;
}

Created by RFdragon

猜你喜欢

转载自www.cnblogs.com/RFdragon/p/10848164.html