一、动态规划
动态规划算法是通过拆分问题,定义问题状态和状态之间的关系,使得问题能够以递推(或者说分治)的方式去解决
明确状态和转移两个问题
二、例题
例一:数字三角形
有一个由非负整数组成的三角形,第一行只有一个数,除了最下行之外每个数的左下方和右下方各有一个数.
1
3 2
4 10 1
4 3 2 20
从第一行的数开始,每次可以往左下或右下走一格,直到走到最下行,把沿途经过的数全部加起来,如何走才能使得这个和尽量大?
输入:三角形的行数n,数字三角形的各个数(从上到下,从左到右)
输出:最大的和。
input 4
1
3 2
4 10 1
4 3 2 20
output 24
状态
f[i][j]表示从(1,1)出发走到(i,j)所有路径的最大和。
转移:
考虑哪些状态对f[i][j]这个状态有影响。
(i-1,j)->(i,j)
(i-1,j-1)->(i,j)
如何转移?
f[i][j]=max(f[i-1][j],f[i-1][j-1])+a[i][j]
把(1,1)到(i,j)的路径分成两类,(1,1)->(i-1,j)->(i,j) 和 (1,1)->(i-1,j-1)->(i,j)
代码:
#include<iostream>
#include<algorithm>
#define maxn 110
#define INF -100000000
using namespace std;
int a[maxn][maxn];
int n;
int main()
{
while(cin>>n&&n)
{
for(int i=0;i<=n+1;i++)
for(int j=0;j<=n+1;j++)
a[i][j]=INF;
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++)
cin>>a[i][j];
for(int i=2;i<=n;i++)
for(int j=1;j<=i;j++)
a[i][j]=a[i][j]+max(a[i-1][j],a[i-1][j-1]);
int maxnn=a[n][1];
for(int i=2;i<=n;i++)
maxnn=max(maxnn,a[n][i]);
cout<<maxnn<<endl;
}
return 0;
}
例二:题目描述
给定一个长度为N的数列,求数值严格单调递增的子序列的长度最长是多少。
示例
input:
7
3 1 2 1 8 5 6
output:
4
答案是:1 2 5 6)
状态:
f[i]表示以i结尾的上升子序列中的最长长度。
转移
f[i]=max(f[j])+1
j<j且a[i]<a[j]
代码:
#include<iostream>
#include<algorithm>
#include<cstring>
#define maxn 210
using namespace std;
int a[maxn],b[maxn];
int n;
int main()
{
while(cin>>n)
{
for(int i=1; i<=n; i++)
cin>>a[i];
for(int i=1; i<=n; i++)
b[i]=1;
for(int i=1; i<=n; i++)
{
int maxnn;
maxnn=0;
for(int j=1; j<i; j++)
{
if(a[j]<a[i])
{
maxnn=max(b[j],maxnn);
}
}
b[i]=maxnn+1;
}
sort(b+1,b+n+1);
for(int i=1;i<=n;i++)
cout<<b[i]<<endl;
}
return 0;
}
优化:O(n*logn)(二分+贪心)//记录最后一个元素的最小值,不断二分求出最完美的答案
代码
#include<iostream>
#include<algorithm>
#define maxn 120
using namespace std;
int a[maxn];
int n;
int minn[maxn];//存储数据最后一个元素的最小值
int main()
{
cin>>n;
for(int i=0;i<n;i++)
cin>>a[i];
for(int i=0;i<n;i++)
minn[i]=-1;
minn[0]=a[0];
int xu=0;
for(int i=1;i<n;i++)
{
if(a[i]>minn[xu])
{
xu++;
minn[xu]=a[i];
}
else
{
int k=lower_bound(minn,minn+xu,a[i])-minn;
minn[k]=a[i];
}
}
cout<<xu+1;
return 0;
}
例三、最长公共子序列
如果Z既是X的子序列,又是Y的子序列,则称Z为X和Y的公共子序列。
状态:a[i][j]=表示截止到第i行第j列最大公共子序列的长度
转移:如果s1[i]!=s2[j],那么a[i][j]=max(a[i][j-1],a[i-1][j]),反之,a[i][j]=a[i-1][j-1]+1
代码
#include<iostream>
#include<cstring>
#include<algorithm>
#define maxn 110
using namespace std;
int N,M;
int a[maxn][maxn];
string s1,s2;
int main()
{
while(cin>>N>>M&&M)
{
cin>>s1>>s2;
memset(a,0,sizeof(a));
for(int i=1;i<=N;i++)
{
for(int j=1;j<=M;j++)
{
if(s1[i]!=s2[j])
a[i][j]=max(a[i-1][j],a[i][j-1]);
else
a[i][j]=a[i-1][j-1]+1;
}
}
int maxx=0;
for(int i=1;i<=N;i++)
for(int j=1;j<=M;j++)
maxx=max(maxx,a[i][j]);
cout<<maxx<<endl;
}
return 0;
}
例题、最大字段和
问题描述
求一个序列的最大子段和即最大连续子序列之和。例如序列[4, -3, 5, -2, -1, 2, 6, -2]的最大子段和为11=[4+(-3)+5+(-2)+(-1)+(2)+(6)]。
问题解析:
因为要用bp来做,所以我们先将他拆分成小问题,开始想的是用a[i][j]表示从i加到j的最大和,但是这样并没有化简,优化后仍是n*n的复杂度,所以我们要换种思路,以a[i]代表以i为结尾的最大子序列和,所以此时转移变成了a[i]=max(0,a[i-1])+a[i]。复杂度为n
代码
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
int main()
{
int N,a[110],ans[110];
while(cin>>N&&N)
{
for(int i=0;i<N;i++)
cin>>a[i];
memset(ans,0,sizeof(ans));
ans[0]=a[0];
for(int i=1;i<N;i++)
ans[i]=max(0,ans[i-1])+a[i];
int maxn=ans[0];
for(int i=1;i<N;i++)
maxn=max(maxn,ans[i]);
cout<<maxn<<endl;
}
return 0;
}
例题:最长公共上升子序列
所得序列应该既满足公共子序列,又满足上升子序列,所以只需要在公共子序列前提下求出上升子序列既可以
代码:复杂度为n的3次方
#include<iostream>
#include<algorithm>
#include<cstring>
#define maxn 110
using namespace std;
int a[maxn],b[maxn],s[maxn][maxn];
int main()
{
int N,M;
while(cin>>N>>M&&M)
{
memset(s,0,sizeof(s));
for(int i=1; i<=N; i++)
cin>>a[i];
for(int j=1; j<=M; j++)
cin>>b[j];
for(int i=1; i<=N; i++)
{
for(int j=1; j<=M; j++)
{
if(a[i]==b[j])
{
for(int k=0; k<j; k++)
if(b[k]<a[i])//保证上升
s[i][j]=max(s[i-1][j],s[i-1][k]+1);
}
else//因为这个地方不用处理公共,所以就算是上升也不用管
s[i][j]=max(s[i-1][j],s[i][j-1]);
}
}
//如果只求最长公共子序列
//应改为
// for(int i=1; i<=N; i++)
// {
// for(int j=1; j<=M; j++)
// {
// if(a[i]==b[j])
// s[i][j]=s[i-1][j-1]+1;
// else
// s[i][j]=max(s[i-1][j],s[i][j-1]);
// }
// }
int maxx=0;
for(int j=1; j<=M; j++)
maxx=max(maxx,s[N][j]);
cout<<maxx<<endl;
}
return 0;
}
改进:第三个循环是为了求出a[i-1][k]的最大值,所以只需要一个常数代替既可以,复杂度为n*n
#include<iostream>
#include<algorithm>
#include<cstring>
#define maxn 110
using namespace std;
int a[maxn],b[maxn],s[maxn][maxn];
int main()
{
int N,M;
while(cin>>N>>M&&M)
{
memset(s,0,sizeof(s));
for(int i=1; i<=N; i++)
cin>>a[i];
for(int j=1; j<=M; j++)
cin>>b[j];
for(int i=1; i<=N; i++)
{
int ma=0;//求s[i-1][k]的最大值
for(int j=1; j<=M; j++)
{
if(a[i]==b[j])
s[i][j]=ma+1;
else
s[i][j]=max(s[i-1][j],s[i][j-1]);
if(a[i]>b[j])
ma=max(ma,s[i-1][j]);
}
}
int maxx=0;
for(int j=1; j<=M; j++)
maxx=max(maxx,s[N][j]);
cout<<maxx<<endl;
}
return 0;
}