最长子序列问题
最长上升子序列及最长下降子序列
某国为了防御敌国的导弹袭击,开发出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭,并观测到导弹依次飞来的高度,请计算这套系统最多能拦截多少导弹。拦截来袭导弹时,必须按来袭导弹袭击的时间顺序,不允许先拦截后面的导弹,再拦截前面的导弹。
输入格式:
第一行,输入雷达捕捉到的敌国导弹的数量k(k<=25),第二行,输入k个正整数,表示k枚导弹的高度,按来袭导弹的袭击时间顺序给出,以空格分隔。
输出格式:
输出只有一行,包含一个整数,表示最多能拦截多少枚导弹。
输入样例:
8
300 207 155 300 299 170 158 65
输出样例:
6
最长公共子序列(LCS)
例如:有字符串s1,s2,按下列方式填表
得到公式:
d
p
[
m
]
[
n
]
=
d
p
[
m
−
1
]
[
n
−
1
]
+
1
;
(
a
[
m
−
1
]
=
=
b
[
n
−
1
]
)
dp[m][n] = dp[m - 1][n - 1] + 1; (a[m - 1] == b[n - 1])
dp[m][n]=dp[m−1][n−1]+1;(a[m−1]==b[n−1])
d
p
[
m
]
[
n
]
=
m
a
x
(
d
p
[
m
−
1
]
[
n
]
,
d
p
[
m
]
[
n
−
1
]
)
;
(
a
[
m
−
1
]
!
=
b
[
n
−
1
]
)
dp[m][n] = max(dp[m - 1][n], dp[m][n - 1]); (a[m - 1] != b[n - 1])
dp[m][n]=max(dp[m−1][n],dp[m][n−1]);(a[m−1]!=b[n−1])
可求出LCS长度。
之后利用回溯法,斜上方的方格中,s1元素等于s2,则跳到斜上方方格,否则向左或者向上走。可求出序列(不一定唯一,只能求出其中一个)
例:两种水果杂交出一种新水果,现在给新水果取名,要求这个名字中包含以前两种水果的字母,且名字尽量短,即:以前的水果名字arr1、arr2是新水果名arr的子序列,使用动态规划的思想设计算法得到新水果名arr。
输入格式:
以空格分开两个水果的名字
输出格式:
新水果的名字
输入样例:
apple peach
输出样例:
appleach
代码如下:
#include<stdio.h>
#include<string.h>
/**
求最大值
**/
int max(int a, int b) {
if (a > b) {
return a;
}
else {
return b;
}
}
/**
动态规划求最长公共子序列
**/
void Lcs(char a[], char b[], int dp[][10]) {
int a_length, b_length;
a_length = strlen(a);
b_length = strlen(b);//求出两个字符串的长度
memset(dp, 0, sizeof(dp));
for (int m = 1; m <= a_length; m++) {//填写m+1行n+1列数组
for (int n = 1; n <= b_length; n++) {
if (a[m - 1] == b[n - 1]) {
dp[m][n] = dp[m - 1][n - 1] + 1;
}
else {
dp[m][n] = max(dp[m - 1][n], dp[m][n - 1]);
}
}
}
}
/**
构建新水果名
**/
int FindLcs(char a[], char b[], int dp[][10],char c[]) {
int i, j ,z= 0;
i = strlen(a); j = strlen(b);
while (i != 0 && j != 0) {
if (a[i - 2] == b[j - 2]) {
c[z] = a[i-1];
i--; j--; z++;
}
else if (dp[i - 1][j] < dp[i][j - 1]) {
c[z] = b[j-1];
j--; z++;
}
else if (dp[i][j - 1] <= dp[i - 1][j]) {
c[z] = a[i-1];
i--; z++;
}
}
return z;
}
int main() {
char a[10]; char b[10]; char c[20];
int i = 0; int a_length = strlen(a);
int b_length = 0;
int dp[10][10];
scanf("%s", &a);
scanf("%s", &b);
Lcs(a, b, dp);
int c_length = FindLcs(a, b, dp,c);
for (int z = c_length -1; z >= 0; z--) {
printf("%c", c[z]);
}
return 0;
}
机器人问题
简单路径规划
规定方向寻找路线条数
如图,从吃冰激凌的皮卡丘走到吃苹果的皮卡丘需要两步:
1、吃冰激凌的皮卡丘------>吃棒棒糖的皮卡丘/喝咖啡的皮卡丘
2、吃棒棒糖的皮卡丘/喝咖啡的皮卡丘------->吃苹果的皮卡丘
公式如下:
a
[
m
]
[
n
]
=
a
[
m
−
1
]
[
n
]
+
a
[
m
]
[
n
−
1
]
a[m][n] = a[m-1][n] +a[m][n-1]
a[m][n]=a[m−1][n]+a[m][n−1]
按照此规律递归填数就ok。
例:一个机器人只能向下和向右移动,每次只能移动一步,设计一个算法求机器人从(0,0)到(m,n)有多少条路径。(目前有个问题,为什么走到(4,5)是35,我觉得是(3,4)是35)
输入格式:
以空格分开m,n
输出格式:
路径条数
输入样例:
4 5
输出样例:
35
int lujing(int m,int n) {
int i,j;
for(i=1;i<=m;i++) {
for(j=1;j<=n;j++) {
a[i][j] = a[i-1][j] + a[i][j-1];
}
}
return a[m-1][n-1];
}
进阶路径规划
除了规定方向还要设置雷区
可考虑把所有路径列出来再删去以禁区为起点到终点的路径(还未实现)
最大乘积问题
I是一个n位十进制整数,要将I划分为k段,找出得到的k个整数的最大乘积,即:
在I中插入k个乘号,使所得积最大。根据DP思想,需要先找到前i位,插入k-1个乘号使得积最大,然后再乘以剩下的位数,找出最大值。
设计方案如下:
设两个二维数组,m[i][k],w[i][i].其中m[i][k]表示从第一位数到第i位数的划分为k个整数的最大乘积,w[i][i]表示i+1到剩下的数字。
得到如下方程:
其中: 1<=d< i (即从1开始一直到i-1 中找最大值)
1、 输入I,k并用一维数组保存I
2、 初始化二维数组w和m
3、 利用DP方程进行求解
代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAXN 50
#define MAXK 10
long m[MAXK][MAXN] = { {0,0} }; /*初始化操作*/
long w[MAXN][MAXN] = { {0,0} };
void maxdp(int n, int k, int *a)
{
int i, j, d, h, q, t, s;
long temp, max;
for (i = 1; i <= n; i++) /*分成1段*/
m[i][1] = w[1][i];
for (i = 1; i <= n; i++) /* DP 过程*/
for (j = 2; j <= k; j++)
{
max = 0;
for (d = 1; d < i; d++)
if ((temp = m[d][j - 1] * w[d + 1][i]) > max)
max = temp;
m[i][j] = max;
}
}
int main()
{
int n, k, i, j;
int a[MAXN] = { 0 }, la = 0;
char c;
scanf("%d %d ", &n, &k);
while ((c = getchar()) != ' ') /*读入数据*/
{
a[++la] = c - '0';
}
for (i = 1; i <= n; i++)
{
w[i][i] = a[i];
for (j = i + 1; j <= n; j++)
w[i][j] = w[i][j - 1] * 10 + a[j];
}
maxdp(n, k, a);
printf("%ld ", m[n][k]);
return 0;
}