讲完递推之后,模拟、二分、队列、栈等垃圾算法我就不讲了,因为实在太简单,一学就会,等到讲到其他算法要用到的时候一并讲。那么先不废话,直接上题。
题目描述: 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