动态规划是个一直以为自己搞懂了,遇到题目又不会的问题,所以到现在也不敢说自己懂了。
总之,能够将一个大问题分解的,就可以考虑动态规划。就是“用问题来回答问题”。
动态规划有四个性质:
看过写得最直白的博客应该是这篇:
通过金矿模型介绍动态规划
动态规划还可以细分为:“线性动态规划”,“区域动态规划”,“树形动态规划”,“背包动态规划”。
我觉得在原理上我实在讲不出来,所以这里给出例子,做练习用,不懂原理的可以参考上面那篇。
先来看几个线性动态规划的栗子:
1.给定一个整数序列,求这个序列的最长上升子序列的长度,子序列可以是不连续的。比如对于序列(1,7,2,8,3,4),它的最长上升子序列是(1,2,3,4)。
#include
#include
using namespace std;
int main()
{
int n;
cin>>n;
int *a=new int[n];
int *dp=new int[n]{1};
for(int i=0;icin>>a[i];
for(int i=1;ifor(int j=0;jif(a[j]1);
}
}
cout<1]<delete []a;
delete []dp;
return 0;
}
2.对于两个字符串,请设计一个时间复杂度为O(m*n)的算法(这里的m和n为两串的长度),求出两串的最长公共子串的长度。这里的最长公共子串的定义为两个序列U1,U2,..Un和V1,V2,…Vn,其中Ui + 1 == Ui+1,Vi + 1 == Vi+1,同时Ui == Vi。
给定两个字符串s1和s2,同时给定两串的长度l1和l2。
#include
#include
#include
#include
using namespace std;
int main()
{
string s1,s2;
int l1,l2;
cin>>s1>>l1>>s2>>l2;
int mx=0;
vector<vector<int>>dp(l1,vector<int>(l2,0));
for(int i=1;ifor(int j=1;jif(s1[i]==s2[j]&&s1[i-1]==s2[j-1])
dp[i][j]=max(dp[i][j],dp[i-1][j-1]+1);
if(mxcout<1<return 0;
}
3.给定一个字符串,从中删除某些字符,让它成为一个平方串(形如“aabaab”,“abcabc”),求最长的平方串长度。比如给定字符串“frankfurt”,它的最长平方串是“frfr”,长度是4。
s=raw_input()
t=0
mx=0
for i in range(len(s)):
t=s.find(s[i],max(i+1,t),len(s))
if t!=-1:
s1=s[0:t]
s2=s[t:len(s)]
dp=[[0 for i in range(len(s2)+1)] for j in range(len(s1)+1)]
for i in range(len(s1)):
for j in range(len(s2)):
if s1[i]==s2[j]:
dp[i+1][j+1]=dp[i][j]+1
else:
dp[i+1][j+1]=max(dp[i][j+1],dp[i+1][j])
if mx<2*dp[len(s1)][len(s2)]:
mx=2*dp[len(s1)][len(s2)]
print mx
4.合唱团:有 n 个学生站成一排,每个学生有一个能力值,牛牛想从这 n 个学生中按照顺序选取 k 名学生,要求相邻两个学生的位置编号的差不超过 d,使得这 k 个学生的能力值的乘积最大,你能返回最大的乘积吗?
#include
#include
#include
using namespace std;
int main()
{
int n;
cin>>n;
int *a=new int[n];
for(int i=0;icin>>a[i];
int k,d;
cin>>k>>d;
vector<vector<long long>>mx(k,vector<long long>(n,0));
vector<vector<long long>>mn(k,vector<long long>(n,0));
for(int i=0;i0][i]=mn[0][i]=a[i];
for(int p=1;pfor(int i=0;ifor(int j=i-1;j>=0&&i-j<=d;j--)
{
mx[p][i]=max(mx[p][i],max(mx[p-1][j]*a[i],mn[p-1][j]*a[i]));
mn[p][i]=min(mn[p][i],min(mn[p-1][j]*a[i],mx[p-1][j]*a[i]));
}
}
}
long long m=0;
for(int i=0;i1][i]);
cout<delete []a;
return 0;
}
然后来看几个背包动态规划的栗子:
1.山洞里有一些不同的草药,采每一株都需要一些时间,每一株也有它自身的价值。 我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。
直接看输入:
输入的第一行有两个整数T(1 <= T <= 1000)和M(1 <= M <= 100),T代表总共能够用来采药的时间,M代表山洞里的草药的数目。
接下来的M行每行包括两个在1到100之间(包括1和100)的的整数,分别表示采摘某株草药的时间和这株草药的价值。
#include
#include
using namespace std;
int main()
{
int t,m;
cin>>t>>m;
int *tm=new int[m],*nm=new int[m];
for(int i=0;icin>>tm[i]>>nm[i];
int *dp=new int[t+1]{0};
for(int i=0;ifor(int j=t;j>=tm[i];j--)
{
dp[j]=max(dp[j],dp[j-tm[i]]+nm[i]);
}
}
cout<delete []tm;
delete []nm;
delete []dp;
return 0;
}
具体来看一个栗子:
有n级的楼梯,每次可以爬一级或者两级,问有多少种不同的爬法:
第一种方法:
如果用递归的思想,这是一个斐波那契数列:
如果有一级楼梯,那么只有一种爬法;
如果有两级楼梯,那么有两种爬法,每次爬一级,或者一次爬两级;
如果有三级楼梯,那么有三张爬法,每次爬一级,先爬一级再爬两级,先爬两级再爬一级;
。。。。。。
可以得出一个递推公式:f(n)=f(n-1)+f(n-2)
代码实现如下:
#include
int digui(int n)
{
if(n==1)
return 1;
else if(n==2)
return 2;
else
return digui(n-1)+digui(n-2);
}
int main()
{
int n;
scanf("%d",&n);
printf("%d\n",digui(n));
return 0;
}
上面的算法,如果有n级台阶,那么运算次数是2^n次,也就是算法复杂度T(n)=O(2^n)。
第二种方法:
如果采用动态规划的方法:
考虑每一级台阶的时候,就问上一级台阶,“你是走两步,还是走一步”
这种“没事走两步”的问法产生了子问题,代码实现如下:
#include
#include
using namespace std;
int main()
{
int n;
cin>>n;
int *dp=new int[n]{0};
dp[0]=1;
dp[1]=2;
for(int i=2;i1]+dp[i-2];//没事走两步
}
cout<1]<delete []dp;
return 0;
}
上面的算法,如果有n级台阶,运算次数是n次,明显可以看出动态规划的优越性。因为时间复杂度是O(n)。