一、相关术语
- 阶段:把问题求解分为若干阶段。
- 状态:每个阶段的状态。
- 转移方程:eg:max{f(i),f(i+1)}
- 边界:初始条件
- 最优子结构
- 无后效性:过去不会影响未来,现在才会影响未来(马尔科夫决策)
- 重叠子问题:重复出现的以求解却需在求解问题
二、是否可以用动态规划
- 计数:有多少种方式?
- 求最大/多/小/少:coin change
- 求存在性:能否选出k个数之和为sum
三、动态规划思想
核心:把最优解分解为若干子问题求解,对子问题继续分解。即对问题进行递推(借助转移方程:可以说是递归方程的另一种变形)。详见下列问题。
1.fibonacci squence(斐波那契数列)
//递归:时间复杂度为O(2^n)
int fib1(int n)
{
if(n==1||n==2)
return 1;
else
return fib1(n-1)+fib1(n-2);
}
//动态规划思想:时间复杂度为O(n)
int fib2(int n)
{
int s,x,y;
x=y=1;
if(n==1||n==2)
return 1;
for(int i=3;i<=n;i++)
{
s=x+y;
x=y;
y=s;
}
}
2.时间Time
3.选不相邻数相加最大
#include <iostream>
using namespace std;
//递归
int rec_opt(int a[],int n)
{
if(n==1)
return a[0];
if(n==2)
return max(a[0],a[1]);
else
{
int b,c;
b=rec_opt(a,n-2)+a[n-1];
c=rec_opt(a,n-1);
return max(b,c);
}
}
//动态规划思想
int dp_opt(int a[],int n)
{
int opt[n];
int i;
if(n==1)
return a[0];
if(n==2)
return max(a[0],a[1]);
opt[0]=a[0];
opt[1]=max(a[0],a[1]);
for(i=2;i<n;i++)
{
opt[i]=max(opt[i-2]+a[i],opt[i-1]);
}
return opt[i-1];
}
int main()
{
int arr[7]={6,10,3,9,6,1,7};
int sum;
//sum=rec_opt(arr,7);
sum=dp_opt(arr,7);
cout<<sum;
return 0;
}
4.求存在性
#include <iostream>
using namespace std;
//递归
bool rec_subset(int a[],int i,int s)
{
if(s==0)
return true;
else if(i==0)
return a[0]==s;
else
{
bool x,y;
x=rec_subset(a,i-1,s-a[i]);
y=rec_subset(a,i-1,s);
return x||y;
}
}
//动态规划
bool dp_subset(int a[],int n,int s)
{
bool opt[n][s];
for(int i=0;i<n;i++)
opt[i][0]=true;
for(int i=0;i<=s;i++)
opt[0][i]=false;
opt[0][a[0]]=true;
for(int i=1;i<n;i++)
for(int j=1;j<=s;j++)
{
if(a[i]>j)
opt[i][j]=opt[i-1][j];
else
{
opt[i][j]=opt[i-1][j]||opt[i-1][j-a[i]];
}
}
return opt[n-1][s];
}
int main()
{
int arr[6]={3,34,4,12,5,2};
bool s;
//s=rec_subset(arr,5,13);
s=dp_subset(arr,6,13);
cout<<boolalpha<<s;
return 0;
}
5.硬币问题
有1元、5元、10元、50元、100元、500元的硬币各C1,C5,C10,C50,C100,C500枚。现在要用这些硬币来支付A元,最少需要多少枚硬币?
对于硬币问题:可以使用贪心算法,但是贪心算法并不一定算出来的就是最少硬币数,只是接近最优解。动态规划一定能算出最优解。eg:如果有10,7,5,1这四种类型的硬币,要付12元,按照贪心算法会为10+1+1,需要3枚,实际最少是7+5,2枚就够了。
#include <iostream>
using namespace std;
//贪心算法
int tanxin(int a[],int n,int s)
{
int sum=0;
for(int i=n-1;i>=0;i--)
{
int t=s/a[i];
s=s-t*a[i];
sum+=t;
}
return sum;
}
//动态规划
int dp_opt(int a[],int n,int s)
{
int opt[s+1];
opt[0]=0;
opt[1]=1;
opt[5]=1;
opt[7]=1;
opt[10]=1;
for(int i=2;i<s+1;i++)
{
int x,y,z,w,m1,m2;
if(i>10)
{
x=opt[i-10]+1;
y=opt[i-7]+1;
z=opt[i-5]+1;
w=opt[i-1]+1;
m1=min(x,y);
m2=min(z,w);
opt[i]=min(m1,m2);
}
else if(i>7&&i!=10)
{
x=opt[i-7]+1;
y=opt[i-5]+1;
z=opt[i-1]+1;
m1=min(x,y);
opt[i]=min(m1,z);
}
else if(i>5&&i!=7&&i!=10)
{
x=opt[i-5]+1;
y=opt[i-1]+1;
opt[i]=min(x,y);
}
else if(i>1&&i!=5&&i!=7&&i!=10)
{
opt[i]=opt[i-1]+1;
}
}
return opt[s-3];
}
int main()
{
int arr[4]={1,5,7,10};
int s=tanxin(arr,4,12);
int s1=dp_opt(arr,4,12);
cout<<s;
cout<<s1;
return 0;
}
6.背包问题
一个旅行者有一个最多能装 M 公斤的背包,现在有 n 件物品,它们的重量分别是W1,W2,…,Wn,它们的价值分别为C1,C2,…,Cn,求旅行者能获得最大总价值。
输入
第一行:两个整数,M(背包容量,M≤200)和N(物品数量,N≤30);
第2…N+1行:每行二个整数Wi,Ci,表示每个物品的重量和价值。
输出
仅一行,一个数,表示最大总价值。
样例输入
10 4
2 1
3 3
4 5
7 9
#include <iostream>
using namespace std;
typedef struct
{
int c;
int w;
}node;
//背包问题的递归算法
int rec_value(node arr[],int n,int m)
{
if(m==0)
return 0;
else if(n==0)
{
if(arr[0].w>m)
return 0;
else
return arr[0].c;
}
else if(arr[n].w>m)
return rec_value(arr,n-1,m);
else
{
int a,b;
a=rec_value(arr,n-1,m);
b=rec_value(arr,n-1,m-arr[n].w)+arr[n].c;
return max(a,b);
}
}
//动态规划
int dp_value(node arr[],int n,int m)
{
int dp[n+1][m+1];
for(int i=0;i<=n;i++)
dp[i][0]=0;
for(int j=0;j<=m;j++)
dp[0][j]=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
if(arr[i-1].w>j)
dp[i][j]=dp[i-1][j];
else
{
dp[i][j]=max(dp[i-1][j],dp[i-1][j-arr[i-1].w]+arr[i-1].c);
}
}
return dp[n][m];
}
int main()
{
int m,n;
node arr[30];
cin>>m>>n;
for(int i=0;i<n;i++)
cin>>arr[i].w>>arr[i].c;
//int maxvalue=rec_value(arr,n-1,m);
int maxvalue=dp_value(arr,n,m);
cout<<maxvalue;
return 0;
}