動態規劃的入門理解
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))))