九.动态规划
(1)递推求解
裴波那契数列
#include<stdio.h>
#include<cstdio>
using namespace std;
const int MAXN = 100;
int Fibonaccil(int n){
int answer;
if(n == 0||n == 1){
answer = n;
}else{
answer = Fibonaccil(n-1)+Fibonaccil(n-2);
}
return answer;
};
int main(){
int n;
scanf("%d",&n);
printf("%d\n",Fibonaccil(n));
}
问题1:N阶楼梯上楼问题:一次可以走两阶或一阶,有多少种上楼方式
#include<stdio.h>
long long F[91];
int main(){
F[1] = 1;
F[2] = 2;
for(int i = 3;i <= 90;i++)
F[i] = F[i-1]+F[i-2];
int n;
while(scanf("%d",&n) != EOF){
printf("%lld\n",F[n]);
}
return 0;
}
问题2:HDU一个网名为8006的男性同学,结交网友无数,最近该同学玩起了浪漫,同时给m个网友每人写了一封信,这都没什么,要命的是,他竟然把所有的信都装错了信封!注意,是全部装错哦!问题是:计算一下一共有多少种可能的错误方式。
分析:n=1时,数量为0;n=2时,数量为1。假设n号信封里装的是k号信封的信,而n号信封里的信则装在m号信封里。按照k和m的等值与否将错装方式分为两类:
(1)k不等于m时,交换n和m信封的信后,n号信封里装的恰好是对应的信,而m号信封中错装k号信封里的信,即除n号信封外其余n-1个信封都错装,错装方式为F[n-1],又由于m有n-1个可能取值,这类错装方式总数为(n-1)*F[n-1].
(2)k等于m时,交换n和m信封后,除它俩之外剩余的n-2个信封全部错装,错装方式为F[n-2],同理,错装方式为(n-1)*F[n-2]
综上所述,递归方式为:F[n] = (n-1)*F[n-1]+(n-1)*F[n-2],即错排公式
#include<stdio.h>
Long long F[21];
int main(){
F[1] = 0;
F[2] = 1;
for(int i = 3;i <= 20;i++)
F[i] = (i-1)*F[i-1]+(i-1)*F[i-2];
int n;
while(scanf("%d",&n) != EOF){
printf("%lld\n",F[n]);
}
return 0;
}
(2)最长递增子序列(LIS)
F[1] = ;
F[1] = max{1,F[j] + 1 | j < i && aj >= ai};
问题3: 某国为了防御敌国的导弹袭击,开发出一种导弹拦截系统.但是这种导弹拦截系统有一个缺陷,虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度.某天,雷达扑捉到敌国的导弹来袭,并观测到导弹依次飞来的高度,计算这套系统最多能拦截多少导弹.(拦截来袭导弹时,必须按来袭导弹袭击的时间顺序,不允许先拦截后面的导弹,再拦截前面的导弹)
#include<stdio.h>
int max(int a,int b){return a>b?a:b;}
int list[26];//按袭击事件顺序保存各导弹高度
int dp[26];//保存以第i个导弹结尾的最长不增子序列长度
int main(){
int n;
while(scanf("%d",&n) != EOF){
for(int i = 1;i <= n;i++){
scanf("%d",&list[i]);
}
for(int i = 1;i < n;i++){//按照袭击时间顺序确定每一个dp[i]
int tmax = 1;//最大值的初始值为1
for(int j = 1;j < i;j++){//遍历其前所有导弹高度
if(list[j] > list[i]){
tmax = max{tmax,dp[j] + 1};//将当前导弹排列在以j号导弹结尾的最长不增子序列之中,计算其长度dp[j] + 1,若大于当前最大值,即更新最大值
}
}
dp[i] = tmax;//将dp[i]保存为最大值
}
int ans = 1;
for(int i = 1;i <= n;i++){
ans = max(ans,dp[i]);
}
printf("%d\n",ans);
}
return 0;
}
时间复杂度为O(n*n),空间复杂度为O(n)
以任意一个数字结束的递增子序列长度,与以排在该数字之前所有比它小的元素结尾的最长递增子序列长度有关,且仅与其数字量有关,而与其具体排列无关
(3)最长公共子序列(LCS)
dp[0][j] (0<=j<=m) = 0;
dp[i][0] (0<=i<=n) = 0;
dp[i][j] = dp[i-1][j-1] + 1; (S1[i] == S2[j])
dp[i][j] = max{dp[i-1][j],dp[i][j-1]}; (S1[i != S2[j]])
问题4: 求解两字符串的最长公共子串长度
#include<stdio.h>
#include<string.h>
int dp[101][101];
int max(int a,int b){return a>b?a:b;}
int main(){
char S1[101],S2[101];
while(scanf("%s%s",S1,S2) != EOF){
int L1 = strlen(S1);
int L2 = strlen(S2);
for(int i = 0;i <= L1;i++) dp[i][0] = 0;
for(int j = 0;j <= L2;j++) dp[0][j] = 0;
for(int i = 1;i <= L1;i++){
for(int j = 1;j <= L2;j++){
if(S1[i-1] != S2[j-1])//若当前两个字符不相等
dp[i][j] = max(dp[i][j-1],dp[i-1][j]);
else dp[i][j] = dp[i-1][j-1] + 1;
}
}
printf("%d\n",dp[L1][L2]);
}
return 0;
}
时间复杂度为O(L1*L2)
问题5: zxr寝室里有n(n<2000)件物品,实在是太多了,于是zxr决定随便搬2k件过去就可以了,每搬一次的疲劳度是和左右手物品的重量差的平方成正比(zxr每次搬两件物品,左手一件,右手一件),zxr想知道搬完这2k件物品后的最佳状态(疲劳度).
分析:假设四个物品质量分别为a,b,c,d(a<=b<=c<=d)
(a)a,b为一组,则疲劳度为: (a-b)(a-b) + (c-d)(c-d)
(b)a,c为一组,则疲劳度为: (a-c)(a-c) + (b-d)(b-d)
两式相减: 2(c-b)*(a-d),为非正数.
于是得出规律,重量最大物品和次大物品一对,重量最小物品和次小一对,不存在交叉情况
状态转移方程为: dp[i][j] = min{dp[i][j - 1],dp[i - 1][j - 2] + (list[j] - list[j-1])^2}
#include<stdio.h>
#include<algorithm>
using namespace std;
#define INF 0x7fffffff //预定义最大的int取值为无穷
int list[2001];//保存每个物品重量
int dp[1001][2001];//保存每个状态
int main(){
int n,k;
while(scanf("%d%d",&n,&k) != EOF){
for(int i = 1;i <= n;i++){
scanf("%d",&list[i);
}
sort(list + 1,list+1+n);//所有物品按重量递增排序
for(int i = 1;i <= n;i++)
dp[0][i] = 0;
for(int i = 1;i <= k;i++){
for(int j = 2*i;j <= n;j++){
if(j > 2*i)
//表明最后两个物品可以不配对,即前j-1件物品足够配成i对,dp[i][j]可以由dp[i][j-1]转移而来,其值被设置为dp[i][j-1]
dp[i][j] = dp[i][j-1];
else
dp[i][j] = INF;
//若j=2*i,则表明最后两件物品必须配对,否则前j件物品配不成i对,其状态不能由dp[i][j-1]转移而来,dp[i][j]先设置为正无穷
if(dp[i][j] > dp[i-1][j-2]+(list[j]-list[j-1])*(list[j]-list[j-1]))
//若dp[i][j]从dp[i-1][j-2]转移而来时,其值优于之前确定的正无穷或者由dp[i][j-1]转移而来的值时,更新该状态
dp[i][j] = dp[i-1][j-2] + (list[j]-list[j-1])*(list[j]-list[j-1]);//更新
}
}
printf("%d\n",dp[k][n]);
}
return 0;
}