动态规划的入门理解
1.动态规划算法的理解:
首先,动态规划问题和分治法问题是很相似的,动态规划就是将带求解问题分解成若干子问题,先求解问题,再结合这些子问题的解得到原问题的解。
与分治法不同的是,适合用动态规划法求解的问题经分解得到的子问题往往不是互相独立的。
优化原则:一个最优决策序列的任何子序列本身一定是相对于子序列的初始和结束状态的
最优的决策序列。
一般的步骤:1.分析最优解的结构。2.建立递归关系(状态转移方程)。3.计算最优值。4.构造最优解。
其中列出状态转移方程是最重要的,如果能列出方程,基本也就可以求出问题的解了。
2.入门题目:
(1)矩阵连乘问题
(2)最长公共子序列
(3)最大字段和
(4)最长递增子序列的长度。
矩阵连乘问题:
给定n个矩阵{A1,A2,...,An},其中Ai与Ai+1是可乘
的,i=1,2...,n-1。如何确定计算矩阵连乘积的计算次序,使得依此次序计算矩阵连乘积需要的数乘次数最少。
输入数据:共m+1行;第一行为测试数据的组数m;以后每行n+1个正整数,表示n个矩阵的行列值。
输出:最少次数及连乘的计算次序。
样例输入:
1
6
5,10,4,6,10,2
样例输出:
348
(A1(A2(A3(A4A5))))
1. 这道题的状态转移方程:
当i != j时,M(i,j) = min{lim(m[i][k]+m[k+1][j]+Pi-1PkPj)};
当i == j时,M(i,j) = 0;
方程说明了,矩阵的最优解与这个矩阵的子问题是密切相关的。
代码实现如下:
<div class="Highlighter">
<pre class="brush:csharp;gutter:true;">
import java.util.Scanner;
public class Main
{
static final int MAX = 100;
static int matrix[] = new int[MAX];
static int n;
static int M[][] = new int[MAX][MAX];//记录各个矩阵的最优解情况
static int s[][] = new int[MAX][MAX];//记录了各个矩阵的最优解的断点位置
public static void main(String []args)
{
Scanner cin = new Scanner(System.in);
int m = cin.nextInt();
for(int i = 0; i < m; i++)
{
n = cin.nextInt();
for(int j = 0 ; j <= n; j++)
{
matrix[j] = cin.nextInt();
}
for(int j = 0; j <= n; j++)
{
for(int k = 0; k <= n; k++)
{
M[j][k] = 0;
s[j][k] = 0;
}
}
DP();//用动态规划的思想来解。
Print1();//输出1-n矩阵的最优解
Print2(1,n);//输出最优解的具体情况
}
}
static void Print1()
{
System.out.println(M[1][n]);
}
static void Print2(int r,int l)
{
if(r + 1 == l)
{
System.out.print("(" + "A" + r + "A" + l + ")");
return;
}
else if(r == l)
{
System.out.print("A" + r);
return;
}
System.out.print("(");
Print2(r,s[r][l]);
Print2(s[r][l]+1,n);
System.out.print(")");
}
static void DP()
{
for(int i = 1; i <= n; i++)
{
M[i][i] = 0;
}
for(int r = 2; r <= n; r++)
{
for(int i = 1; i + r-1<= n; i++)
{
int l = i+r-1;
M[i][l] = M[i][i]+M[i+1][l]+matrix[i]*matrix[i-1]*matrix[l];
s[i][l] = i;
for(int k = i+1; k < l; k++)
{
int temp = M[i][k]+M[k+1][l]+matrix[k]*matrix[i-1]*matrix[l];
if(temp < M[i][l])
{
M[i][l] = temp;
s[i][l] = k;
}
}
}
}
}
}
</pre>
</div>
分析:很明显这样的解法,时间复杂度是为N3的,当n趋向于无穷大的时候,
这个算法是算不出来的,还是需要继续优化下去的。具体的解答,请自行去实验
最长公共子序列:
输入:
输入第一行给出一个整数N(0<N<100)表示待测数据组数。接下来每组数据两行,分别为待测的两组字符串。每个字符串长度不大于1000.。
输出每组测试数据输出一个整数,表示最长公共子序列长度。每组结果占一行。
样例输入
2
asdf
adfsd
123abc
abc123ab
样例输出
3
6
优化函数的状态转移方程:
C[i,j] = { 0,i >0 && j = 0}
{c[i-1][j-1] + 1} i>0 && j > 0 && xi = yi;
{max{x[i][j-1],x[i-1][j]}} i,j > 0; xi != yi;
方程的意思是:当A,B两个序列的最后一个数相等的时候,等于A-1,B-1这两个序列的最优解加上1,如何不等,等于{A-1,B},{A,B-1}这两个最优解中的最小值。
代码实现如下:
<div class="Highlighter">
<pre class="brush:csharp;gutter:true;">
递归实现如下:
import java.util.Scanner;
public class Main
{
static String str1,str2;
public static void main(String []args)
{
Scanner cin = new Scanner(System.in);
int N = cin.nextInt();
for(int i = 0; i < N; i++)
{
str1 = cin.next();
str2 = cin.next();
System.out.println(LcsLength(str1,str2,str1.length()-1,str2.length()-1));
}
}
static int LcsLength(String str1,String str2,int len1,int len2)
{
if(len1 == -1 || len2 == -1)
{
return 0;
}
else if(str1.charAt(len1) == str2.charAt(len2))
{
return (1+LcsLength(str1,str2,len1-1,len2-1));
}
else
{
int a = LcsLength(str1,str2,len1-1,len2);
int b = LcsLength(str1,str2,len1,len2-1);
return Math.max(a, b);
}
}
}
非递归实现如下:
import java.util.Scanner;
public class Main
{
static final int MAX = 105;
static int c[][] = new int[MAX][MAX];
public static void main(String []args)
{
Scanner cin = new Scanner(System.in);
int N = cin.nextInt();
for(int i = 0; i < N; i++)
{
String str1,str2;
str1 = cin.next();
str2 = cin.next();
int len1 = str1.length();
int len2 = str2.length();
LcsLength(str1,str2,len1,len2);
System.out.println(c[len1][len2]);
}
}
static void LcsLength(String str1,String str2,int len1,int len2)
{
for(int i = 0; i <= len1; i++)
{
for(int j = 0; j <= len2; j++)
{
c[i][j] = 0;
}
}
for(int i = 1; i <= len1; i++)
{
for(int j = 1; j <= len2; j++)
{
if(str1.charAt(i-1) == str2.charAt(j-1))
{
c[i][j] = c[i-1][j-1]+1;
}
else if(c[i-1][j] >= c[i][j-1])
{
c[i][j] = c[i-1][j];
}
else
{
c[i][j] = c[i][j-1];
}
}
}
}
}
</pre>
</div>
最大字段和:
输入:
输入的第一行包含一个整数m,表示下面共有m组测试数据。每组测试数据一共包含两行数据,第1行给出正整数K( < 10000 ),第2行给出K个整数,中间用空格分隔。当K为0时,输入结束,该用例不被处理.
输出:在1行里输出最大和。若所有K个元素都是负数,则定义其最大和为0。
状态转移方程如下:
dp[i] = {num[i],dp[i-1] <= 0}
{num[i]+dp[i-1],dp[i-1] > 0}
方程意思是:
用前一个数的正负来决定这个数的大小。
代码实现如下:
<div class="Highlighter">
<pre class="brush:csharp;gutter:true;">
import java.util.Arrays;
import java.util.Scanner;
public class Main
{
static final int MAX = 10005;
static int num[] = new int[MAX];
static int dp[] = new int[MAX];
public static void main(String []args)
{
Scanner cin = new Scanner(System.in);
int m = cin.nextInt();
for(int i = 0; i < m; i++)
{
int K = cin.nextInt();
if(K == 0)
{
continue;
}
for(int j = 0; j < K; j++)
{
num[j] = cin.nextInt();
}
Arrays.fill(dp, 0);
DP(K);
}
}
static void DP(int n)
{
dp[0] = num[0];
for(int i = 1; i < n; i++)
{
if(dp[i-1] > 0)
{
dp[i] = dp[i-1]+num[i];
}
else
{
dp[i] += num[i];
}
}
int output = 0;
for(int i = 0; i < n; i++)
{
output = Math.max(output, dp[i]);
}
System.out.println(output);
}
}
</pre>
</div>
最长递增子序列的长度:
求一个字符串的最长递增子序列的长度。请分别给出最长递增子序列长度的O(n2)及O(nlogn)算法。
如:dabdbf最长递增子序列就是abdf,长度为4
输入
第一行一个整数0<n<20,表示有n个字符串要处理
随后的n行,每行有一个字符串,该字符串的长度不会超过10000
输出
输出字符串的最长递增子序列的长度
样例输入
3
aaa
ababc
abklmncdefg
样例输出
1
3
7
状态转移方程如下:
D[i] = max{D[0-i]},条件是递增才行。
<div class="Highlighter">
<pre class="brush:csharp;gutter:true;">
时间复杂度为N2的代码如下:
import java.util.Scanner;
public class Main
{
static final int MAX = 10005;
static int dp[] = new int[MAX];
public static void main(String []args)
{
Scanner cin = new Scanner(System.in);
int n = cin.nextInt();
for(int i = 0; i < n; i++)
{
String str = cin.next();
Dp(str);
}
}
static void Dp(String str)
{
dp[0] = 1;
int len = str.length();
int Max = 0;
for(int i = 1; i < len; i++)
{
Max = 0;
for(int j = 0; j < i; j++)
{
if(str.charAt(j) < str.charAt(i))
{
Max = Math.max(Max, dp[j]);
}
}
dp[i] = Max+1;
}
Max = 0;
for(int i = 0; i < len; i++)
{
Max = Math.max(Max, dp[i]);
}
System.out.println(Max);
}
}
时间复杂度为nlogn的算法实现如下:
import java.util.Scanner;
public class Main
{
static final int MAX = 10005;
static char d[] = new char[MAX];
public static void main(String []args)
{
Scanner cin = new Scanner(System.in);
int n = cin.nextInt();
for(int i = 0; i < n; i++)
{
String str = cin.next();
int len = str.length();
DpPrint(str,len);
}
}
static void DpPrint(String str,int len)
{
d[0] = str.charAt(0);
int cnt = 0;
int j = 0;
for(int i = 1; i < len; i++)
{
if(str.charAt(i) > d[cnt])
{
cnt++;
j = cnt;
}
else if(str.charAt(i) == d[j])
{
continue;
}
else
{
j = BinSearch(str.charAt(i),0,cnt)+1;
}
d[j] = str.charAt(i);
}
System.out.println(cnt+1);
}
static int BinSearch(int temp,int l,int r)
{
while(l <= r)
{
int mid = (r+l)/2;
if(d[mid] < temp && temp <= d[mid+1])
{
return mid;
}
else if(d[mid] < temp)
{
l = mid+1;
}
else
{
r = mid-1;
}
}
return -1;
}
}
</pre>
</div>
1)矩阵链连乘问题: 给定n个矩阵{A1,A2,...,An},其中Ai与Ai+1是可乘
的,i=1,2...,n-1。如何确定计算矩阵连乘积的计算次序,使得依此次序计算矩阵连乘积需要的数乘次数最少。
输入数据:共m+1行;第一行为测试数据的组数m;以后每行n+1个正整数,表示n个矩阵的行列值。
输出:最少次数及连乘的计算次序。
样例输入:
1
5,10,4,6,10,2
样例输出:
348
(A1(A2(A3(A4A5))))