斐波那契数列
题目11.2链接Fibonacci Number
解决斐波那契数列有三种方法
1>直接递归
fib(n)
if n==0 || n==1
return 1
return fib(n-1)+fib(n-2)
2>记忆化递归
fib(n)
if n==0 || n==1
return F[n] = 1
if F[n]
return F[n]
return F[n] = fib(n-1)+fib(n-2)
3>动态规划
fib()
F[0] = 1
F[1] = 1
for i = 2-n
F[i] = F[i-1]+F[i-2]
代码如下:
#include <iostream>
#include <cstring>
using namespace std;
const int Max = 50;
int F[Max];
int Fib(int n)
{
F[0] = 1;
F[1] = 1;
if(n==0 || n==1) return F[n];
if(F[n])
return F[n];
return F[n] = Fib(n-1)+Fib(n-2);
}
void makeFib()
{
F[0] = 1;
F[1] = 1;
for(int i=2; i<Max; i++)
{
F[i] = F[i-1]+F[i-2];
}
}
int main()
{
int n;
cin >> n;
memset(F, 0, sizeof(F));
cout << Fib(n) << endl;
}
最长公共子列(LCS)
题目11.3链接Longest Common Subsequence
解析:可以设置以下变量
变量 | 含义 |
---|---|
c[m+1][n+1] | c[i][j]表示 X i 与 Y j X_{i}与Y_{j} Xi与Yj的LCS长度 |
求 X i 与 Y j X_{i}与Y_{j} Xi与Yj的LCS时要考虑两种情况
- x m = y n x_{m}=y_{n} xm=yn时, X m − 1 与 Y n − 1 X_{m-1}与Y_{n-1} Xm−1与Yn−1的LCS需要加一
- x m ! = y n x_{m}!=y_{n} xm!=yn时,取 X m 与 Y n − 1 X_{m}与Y_{n-1} Xm与Yn−1的LCS和 X m − 1 与 Y n X_{m-1}与Y_{n} Xm−1与Yn的LCS的更大者
c
[
i
]
[
j
]
=
{
0
,
i
=
0
o
r
j
=
0
c
[
i
−
1
]
[
j
−
1
]
+
1
,
i
,
j
>
0
a
n
d
x
i
=
y
j
m
a
x
(
c
[
i
]
[
j
−
1
]
,
c
[
i
−
1
]
[
j
]
)
,
i
,
j
>
0
a
n
d
x
i
!
=
y
j
c[i][j] = \begin{cases} 0 & & ,i=0 or j=0 \\ c[i-1][j-1]+1 & & ,i,j>0 and x_{i}=y_{j}\\ max(c[i][j-1], c[i-1][j]) & & ,i,j>0 and x_{i}!=y_{j}\\ \end{cases}
c[i][j]=⎩⎪⎨⎪⎧0c[i−1][j−1]+1max(c[i][j−1],c[i−1][j]),i=0orj=0,i,j>0andxi=yj,i,j>0andxi!=yj
代码如下:
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int Max = 1005;
int c[Max][Max];//下标代表长度
void lcs(string s1, string s2)
{
//当i或j等于0的时候,初始化
for(int i=0; i<=s1.size(); i++)
{
c[i][0] = 0;
}
for(int i=0; i<=s2.size(); i++)
{
c[0][i] = 0;
}
s1 = " " + s1;//为了与c数组下标配齐,需要加一个头元素
s2 = " " + s2;
//动态规划
for(int i=1; i<=s1.size(); i++)
{
for(int j=1; j<=s2.size(); j++)
{
if(s1[i]==s2[j]) c[i][j] = c[i-1][j-1]+1;
else
{
c[i][j] = max(c[i][j-1], c[i-1][j]);
}
}
}
}
int main()
{
int n;
cin >> n;
string s1, s2;
for(int i=0; i<n; i++)
{
cin >> s1 >> s2;
lcs(s1, s2);
cout << c[s1.size()][s2.size()] << endl;
}
return 0;
}
最长递增子序列(LIS)
题目17.3链接Longest Increasing Subsequence
参考博文求LIS的两种方法:DP 与 二分法 ~~
LIS有两种方法,DP(O(N^2))和二分法(O(Nlog(N)),当数据量较大时使用二分法。
解析:可以设置以下的变量
变量 | 含义 |
---|---|
L[i] | L[i]表示长度为i的递增子序列的末尾元素最小值 |
l e n g t h i length_{i} lengthi | 表示前i个元素构成的最长递增子序列的长度 |
二分搜索法:
思路:我们过程是一直更新L数组,有两种情况
- A[i]>L[length-1] 则扩展L数组,L[++length] = A[i]。(因为A[i]可以扩展递增子序列的长度,所以加入该序列)
- A[i]<=L[length-1] 则更新L数组,二分搜索更新L数组中第一个大于等于A[i]的元素为A[i]。(因为L[i]代表的是长度为i的递增子序列的末尾元素(且是所有递增子序列中末尾元素最小的那一个),L[i]中小于A[i]的元素均可以与A[i]组成递增子序列,所以可以使用A[i]更新第一个大于等于它的元素)。
代码如下:
#include <iostream>
#include <algorithm>
using namespace std;
const int MAX = 100005;
int n, length;
int A[MAX], L[MAX];
void LIS()
{
L[0] = A[0];
length = 1;
for(int i=1; i<n; i++)
{
if(L[length-1]>=A[i])
{
int *p = lower_bound(L,L+length,A[i]);
*p = A[i];
}
else
{
L[length++] = A[i];
}
}
}
int main()
{
cin >> n;
for(int i=0; i<n; i++)
{
cin >> A[i];
}
LIS();
cout << length << endl;
return 0;
}
- 将L[]初始化为INF能够简化代码
//核心代码
int L[MAX]
void solve()
{
fill(L, L+n, INF);//填充函数
for(int i=0; i<n; i++)
{
*lower_bound(L, L+n, INF) = L[i];
}
printf("%d\n", lower_bound(L, L+n, INF) - dp);//计算长度
DP法:
- 构造状态:L[i]代表以第i个字符结尾的字符串的最大递增子串的长度。
- 终态:max(L[i]),i>0 && i<=N
- 状态转化方程:L[i] = max{1 , L[j]+1} ( j<i && A[j]<A[i] )
- 初态:L[i] = 1,i>0 && i<=N
核心代码:
int Max = 1;
for(int i=1; i<=p; i++)
L[i] = 1;
for(int i=2; i<=p; i++)
{
for(int j=0; j<i; j++)
{
if(x[i]>x[j])
L[i] = max(L[i], L[j]+1);
}
if(Max<L[i]) Max = L[i];
}
cout << Max << endl;
矩阵链乘法
题目11.4链接Matrix Chain Multiplication
解析:可以设置以下变量
变量 | 含义 |
---|---|
m[n+1][n+1] | m[i][j]表示计算 ( M i M i + 1 . . . M j ) (M_{i}M_{i+1}...M_{j}) (MiMi+1...Mj)是所需乘法运算的最小次数 |
p[n+1] | 用于存储矩阵的行列数,其中 M i 是 p [ i − 1 ] × p [ i ] M_{i}是p[i-1] \times p[i] Mi是p[i−1]×p[i]的矩阵 |
思路是遍历所有可能的分割情况,得出
(
M
i
M
i
+
1
.
.
.
M
j
)
(M_{i}M_{i+1}...M_{j})
(MiMi+1...Mj)的最小运算次数,即
m
[
i
]
[
j
]
=
{
0
,
i
=
j
m
i
n
i
≤
k
<
j
(
m
[
i
]
[
k
]
+
m
[
k
+
1
]
[
j
]
+
p
[
i
−
1
]
×
p
[
k
]
×
p
[
j
]
)
,
i
<
j
m[i][j] = \begin{cases} 0 & & ,i=j \\ min_{i \leq k <j}(m[i][k]+m[k+1][j]+p[i-1]\times p[k]\times p[j]) & & ,i<j\\ \end{cases}
m[i][j]={0mini≤k<j(m[i][k]+m[k+1][j]+p[i−1]×p[k]×p[j]),i=j,i<j
- 递推法:
代码如下:
#include <iostream>
using namespace std;
const int Max = 200;
int p[Max];
int m[Max][Max];
int main()
{
int n;
cin >> n;
for(int i=1; i<=n; i++)
{
cin >> p[i-1] >> p[i];
}
for(int i=1; i<=n; i++)//初始化
m[i][i] = 0;
for(int l=2; l<=n; l++)//l代表矩阵链的长度,从2到n
{
for(int i=1; i<=n-l+1; i++)//i为矩阵链起始下标
{
int j=i+l-1;//j为矩阵链末尾下标
m[i][j] = (1<<21);//初始为极大的值
for(int k=i; k<j; k++)//遍历出最优解
{
m[i][j] = min(m[i][j], m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j]);
}
}
}
cout << m[1][n] << endl;
return 0;
}
- 记忆化搜索法
代码如下:
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
using namespace std;
const int MAX = 105;
const int INF = 1<<29;
struct Matrix
{
int r, c;
};
Matrix M[MAX];
int dp[MAX][MAX], n;
int DP(int i, int j)
{
int &ans = dp[i][j];
if(ans!=-1) return ans;
ans = INF;
for(int k=i; k<j; k++)
{
ans = min(ans, DP(i, k)+DP(k+1, j)+M[i].r*M[k].c*M[j].c);
}
return ans;
}
int main()
{
scanf("%d", &n);
memset(dp, -1, sizeof(dp));
for(int i=1; i<=n; i++)
{
scanf("%d%d", &M[i].r, &M[i].c);
dp[i][i] = 0;
}
printf("%d\n", DP(1, n));
return 0;
}
硬币问题
题目17.1链接 Coin Changing Problem
解析:可以设置以下的变量
变量 | 含义 |
---|---|
C[i] | C[i]表示第i种硬币的面值 |
T[i][j] | T[i][j]表示使用第0至第i种硬币支付j时的最少硬币枚数 |
可得递归式子:
T[i][j] = min(T[i-1][j], T[i][j-C[i]]+1)
T[i-1][j]代表不使用第i种硬币, T[i][j-C[i]]+1代表使用第i种硬币,去二者最小的即可。
在T矩阵中可知,其值由其正上方的值和其左边的值决定
,所以可以使用动态规划,并简化T为一维数组,从小到大一次求值。
变量 | 含义 |
---|---|
T[n] | T[j]表示支付j元的硬币最少枚数 |
递归公式可化为:
T[j] = min(T[j], T[j-C[i]]+1)
代码如下:
#include <iostream>
#include <algorithm>
using namespace std;
const int Max1 = 50005;
const int Max2 = 30;
const int INF = (1<<29);
int C[Max2], T[Max2][Max1];
int T2[Max1];
int main()
{
int n, m;
cin >> n >> m;
for(int i=1; i<=m; i++)
{
cin >> C[i];
}
//sort(C+1, C+m+1);//没必要排序
for(int j=0; j<=n; j++)
{
T2[j] = INF;
}
T2[0] = 0;
for(int i=1; i<=m; i++)//动态规划存数组
{
for(int j=C[i]; j<=n; j++)
{
T2[j] = min(T2[j], T2[j-C[i]]+1);
}
}
cout << T2[n] << endl;
return 0;
}
/*二维数组
int main()
{
int n, m;
cin >> n >> m;
for(int i=1; i<=m; i++)
{
cin >> C[i];
}
sort(C+1, C+m+1);
for(int j=0; j<=n; j++)
{
T[0][j] = INF;
}
for(int i=0; i<=m; i++)
{
T[i][0] = 0;
}
for(int i=1; i<=m; i++)
{
for(int j=1; j<=n; j++)
{
if(j>=C[i])
T[i][j] = min(T[i-1][j], T[i][j-C[i]]+1);
else
T[i][j] = T[i-1][j];
}
}
cout << T[m][n] << endl;
return 0;
}
*/
0-1背包问题
题目17.2链接 0-1 Knapsack Problem
解析:可以设置以下的变量
变量 | 含义 |
---|---|
items[N+1] | 是一个结构体数组,item[i].v、item[i].w分别记录第i个物品的价值和重量 |
C[N+1][W+1] | C[i][w]表示前i个物品装入容量为w的背包是总价值的最大值 |
思路:因为每种只有一个物品,所以就是选择放与不放
C
[
i
]
[
w
]
=
{
0
,
i
=
0
o
r
w
=
0
m
a
x
(
C
[
i
−
1
]
[
w
]
,
C
[
i
−
1
]
[
w
−
i
t
e
m
[
i
]
.
w
]
+
i
t
e
m
[
i
]
.
v
)
,
i
t
e
m
[
i
]
.
w
<
=
w
C
[
i
−
1
]
[
w
]
,
i
t
e
m
[
i
]
.
w
>
w
C[i][w] = \begin{cases} 0 & & ,i=0 or w=0 \\ max(C[i-1][w], C[i-1][w-item[i].w]+item[i].v) & & ,item[i].w<=w\\ C[i-1][w] & & ,item[i].w>w \end{cases}
C[i][w]=⎩⎪⎨⎪⎧0max(C[i−1][w],C[i−1][w−item[i].w]+item[i].v)C[i−1][w],i=0orw=0,item[i].w<=w,item[i].w>w
代码如下:
#include <iostream>
using namespace std;
const int Max = 200;
const int W = 10005;
struct Node
{
int v, w;
};
Node item[Max];
int C[Max][W];
int main()
{
int N, W;
cin >> N >> W;
for(int i=1; i<=N; i++)
{
cin >> item[i].v >> item[i].w;
}
for(int i=0; i<=N; i++)//初始化
{
C[i][0] = 0;
}
for(int w=0; w<=W; w++)
{
C[0][w] = 0;
}
for(int i=1; i<=N; i++)
{
for(int w=1; w<=W; w++)
{
if(item[i].w<=w)//放的下
{
C[i][w] = max(C[i-1][w], C[i-1][w-item[i].w]+item[i].v);
}
else
{
C[i][w] = C[i-1][w];
}
}
}
cout << C[N][W] << endl;
return 0;
}
最大正方形
题目17.4链接Largest Square
解析:可以设置以下的变量
变量 | 含义 |
---|---|
dp[H][W] | dp[i][j]存储着从(i,j)向左上方扩展可形成的最大正方形的边长。 |
G[H][W] | G[i][j]存储着输入的图(i,j)元素 |
dp[i][j]的值等于其左上、上方。左侧元素中最小的值加一.
其中初始化时,dp[0][j] = 1-G[0][j],dp[i][0] = 1-G[i][0]。
dp[i][j] = min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1])+1.
代码如下:
#include <iostream>
using namespace std;
const int Max = 1500;
int G[Max][Max], dp[Max][Max];
int H, W, LS;
int main()
{
cin >> H >> W;
LS = 0;//最大正方形边长
for(int i=0; i<H; i++)
{
for(int j=0; j<W; j++)
{
cin >> G[i][j];
}
}
for(int i=0; i<H; i++)//初始化
{
dp[i][0] = 1-G[i][0];
LS = max(dp[i][0], LS);
}
for(int j=0; j<W; j++)
{
dp[0][j] = 1-G[0][j];
LS = max(dp[0][j], LS);
}
//动态规划
for(int i=1; i<H; i++)
{
for(int j=1; j<W; j++)
{
if(G[i][j])
dp[i][j] = 0;
else
{
dp[i][j] = min(dp[i-1][j-1], min(dp[i-1][j], dp[i][j-1]))+1;
}
LS = max(LS, dp[i][j]);
}
}
cout << LS*LS << endl;
return 0;
}
最大长方形
题目17.5链接Largest Rectangle
解析:可以设置以下的变量
变量 | 含义 |
---|---|
S | S是栈,其中的元素为一个结构体(包括元素高度,和矩形最左的边界) |
栈+DP
四种情况:rect为结构体的一个元素
- 栈为空-将rect压入栈
- 栈顶长方形的高小于rect的高-将rect压入栈
- 栈顶长方形的高等于rect的高-不做处理
- 栈顶长方形的高大于rect的高-更新最大长方形的面积
具体解法见代码:
#include <iostream>
#include <algorithm>
#include <cstring>
#include <stack>
using namespace std;
const int Max = 1500;
struct Node//结构体,height为元素的高度,pos为包括该元素的矩形的最左边界
{
int height, pos;
};
int H, W;
int G[Max][Max], T[Max][Max];//G为输入图,T为标记图
int main()
{
cin >> H >> W;
for(int i=0; i<H; i++)
{
for(int j=0; j<W; j++)
{
cin >> G[i][j];
}
}
//标记
for(int j=0; j<W; j++)
{
for(int i=0; i<H; i++)
{
if(G[i][j])//有污迹
{
T[i][j] = 0;
}
else
{
if(i==0)//第一行
T[i][j] = 1;
else//计算高度
T[i][j] = T[i-1][j]+1;
}
}
}
//栈式动态规划
stack<Node> S;
int maxv = 0, area = 0;
for(int i=0; i<H; i++)
{
T[i][W] = 0;//用于计算j=W-1时加入新矩形的更新
for(int j=0; j<=W; j++)
{
Node rect;
rect.height = T[i][j];
rect.pos = j;
if(S.empty())
S.push(rect);
else
{
if(S.top().height<rect.height)
{
S.push(rect);
}
else if(S.top().height>rect.height)
{
int target = j;
while(!S.empty() && S.top().height>=rect.height)//一直操作到栈中元素height小于rect的height
{
Node pre = S.top(); S.pop();
area = (j-pre.pos)*pre.height;//计算面积
maxv = max(maxv, area);
target = pre.pos;//更新矩形的最左边界
}
rect.pos = target;
S.push(rect);//更新完面积后入栈
}
}
}
}
cout << maxv << endl;
return 0;
}
背包问题
参考博文背包问题小总结 习题(动态规划01背包(第k优解)完全背包,多重背包)
- 注意例题2的将问题分成两个子问题的解决方法。
- 01背包第k个最优解
int i,j,t,k;//k是第k个最优解
for(i=0;i<n;i++) //对每个物品扫描
for(j=v;j>=vol[i];j--) //对每个状态进行更新
{
for(t=1;t<=k;t++)
{ //把所有可能的解都存起来
a[t]=f[j][t];//f[j][0]是该序列中最优的
b[t]=f[j-vol[i]][t]+val[i];//t=0时也是该序列中最优的
}
int m,x,y;
m=x=y=1;
a[k+1]=b[k+1]=-1;
//下面的循环相当于求a和b并集,也就是所有的可能解 (求出a和b中前k优解,由m控制,按顺序从优到次)
while(m<=k && (a[x]!=-1 || b[y]!=-1))
{
if(a[x]>b[y])
f[j][m]=a[x++];
else
f[j][m]=b[y++];
if(f[j][m]!=f[j][m-1])
m++;
}
}