目录
算法
动态规划dp
1.核心:最优子结构
大问题的最优解由小问题的最优解推出
2.性质:无后效性
一旦 f(n) 确定,之后直接调用,不再关心f(n)计算过程
3.构成:设计状态(边界,终止条件)+设计动态转移(递推式)
如f(n)由f(n-1)或f(n-2)推来
经典例题:硬币问题
描述:共1,5,11面值的硬币,求组成15元的时候,使用最少的币数
1.原理:f(n)=min( f(n-1)+1, f(n-5)+1, f(n-11)+1 )
f(n)只与f(n-1),f(n-5),f(n-11)有关;
2.构成:
dp【n】=cost:价值为n时的最优解;
下标:组成的价值n,值:需硬币个数为cost;
dp【n-1】+1:价值n-1时的最优解+一个价值为1的硬币 (总价值为n)
dp【n-5】+1:价值n-5时的最优解+一个价值为5的硬币 (总价值为n)
dp【n-11】+1:价值n-11时的最优解+一个价值为11的硬币 (总价值为n)
3.伪代码:min,max找最优解+分类讨论
if(i-1>=0) cost=min(cost,dp[i-1]+1);
if(i-5>=0) cost=min(cost,dp[i-5]+1);
if(i-11>=0) cost=min(cost,dp[i-11]+1);
int min(int a,int b)
{
return a<b?a:b;
}
int main()
{
int dp[100],i; //dp【n】价值为n时的最优解硬币个数
int cost; //花费硬币数量
dp[0]=0; //边界
for(i=1; i<=15; i++) //满15元(价值15)
{
cost=99999999; //每次重置cost
if(i-1>=0) cost=min(cost,dp[i-1]+1);
if(i-5>=0) cost=min(cost,dp[i-5]+1);
if(i-11>=0) cost=min(cost,dp[i-11]+1); //dp【n】+1代表价值n的最优解+价值1
dp[i]=cost; //硬币个数
printf("%d",dp[i]);
}
}
01背包
每件物品仅一件01
问题描述:
假设背包的容量是 C=10,求一定容量内能装物品价值最大为?
物品编号:1 2 3
物品重量:5 6 4
物品价值:20 10 12
1.含义:
v【i】:第 i 件物品价值;
w【i】:第 i 件物品重量;
dp【i】【j】:可装前 i 件物品时,所能承受重量 j ,最大总价值
2.底层条件:dp【0】【j】=0;
3.递推公式:拿or不拿?dp[i][j]=max( (dp[i-1][ j−w[i] ] ) + v[i] , dp[i-1][j])
拿:dp【i】【j】=dp【i-1】【j-w【i】】+v【i】
不拿:dp【i】【j】=dp【i-1】【j】
4.注意:容量逆序,for(int j=t ; j>=0 ; j--)
#include <iostream>
using namespace std;
int w[105],v[105];
int dp[105][1005];
int main()
{
int t,m,res;
scanf("%d%d",&t,&m);
for(int i=1;i<=m;i++)
{
scanf("%d%d",&w[i],&v[i]);
}
for(int i=1;i<=m;i++)
{
for(int j=t;j>=0;j--) //容量逆序
{
if(j>=w[i]) //所剩容量大于需装物体体积
{
dp[i][j]=max(dp[i-1][j-w[i]]+v[i],dp[i-1][j]); //装or不装
}
else //只能不装
{
dp[i][j]=dp[i-1][j];
}
}
}
printf("%d",dp[m][t]);
return 0;
}
滚动数组优化
定义: 数组内数据滚动起来(实时更新),二维dp简化为一维dp
原理:由于每次更新dp【i】【】数组,只需知道dp【i-1】【】信息,故可利用一维滚动dp数组优化
注意:反向枚举体积,逆序;
如若正序更新滚动数组,则会出现dp【i】【】使用更新后的dp【i-1】【】,导致结果出错
for(int i=0;i<=v;i++)
{
dp[i]=0; //边界处理
}
for(int i=1;i<=N;i++)
{
for(int j=V;j>=w[i];j--) //逆序!!!
{
dp[j]=max(v[i]+dp[j-w[i]],dp[j]);
}
}
//放得下:v【i】+dp【j-w[i]】
//放不下:dp【j】
刷题
1.金字塔最大价值路径
1.题意:顶部到底部最大价值的路径,求最大价值和
2.构成:a【】【】存图金字塔价值,dp【】【】当前价值,maxx中最大价值和
3.输入:输入金字塔,存入a【】【】数组
for(int i=1; i<=n ;i++)
for(int j=1; j<=i ;j++)
4.底层条件:初始化边界值(终止条件)
5.递推式:// 上一点A向右走或向下走到下一点B
dp[i][j]=max( dp[i-1][j] , dp[i-1][j-1] )+a[i][j]
6.更新最大值maxx:尝试不同路,保留每步step最优解
7.注意:价值求和加到尾巴上
#include<stdio.h>
int max(int a,int b)
{
return a>b?a:b;
}
int main()
{
int a[1001][1001],dp[1001][1001];
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++) //金字塔输入
{
scanf("%d",&a[i][j]);
}
int maxx=0;
dp[1][1]=a[1][1]; //边界值
for(int i=2;i<=n;i++) //从2开始,1为起点
for(int j=1;j<=n;j++)
{
dp[i][j]=max(dp[i-1][j],dp[i-1][j-1])+a[i][j]; //状态转移方程 + 求最大价值的和
maxx=max(maxx,dp[i][j]); //更新最大价值
}
printf("%d\n",maxx);
return 0;
}
2. 过河卒
P1002 [NOIP2002 普及组] 过河卒 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)https://www.luogu.com.cn/problem/P1002
1.题意:二维地图,跳过马从起点到终点的路径条数;
2.关键:加套两层边界
3.构成:
bx,by:终点B
hx,hy:马王点
horse【】【】:存马的位置
f【i】【j】:走到终点(i,j)的最优解
4.初始化边界:f[1][2]=1;初始最初的最初(含加套的两层)
5.注意:
马的分布规律(四周日字),考虑马位于边界情况,故加套两层边界
起点f【2】【2】不可直接设为1
数据类型:long long
#include<stdio.h>
int main()
{
long long horse[30][30]= {0},bx,by,hx,hy, f[30][30]; //long long
scanf("%lld%lld%lld%lld",&bx,&by,&hx,&hy);
bx+=2;
by+=2;
hx+=2;
hy+=2; //对边界加两层边套(马的特性)
for(int i=2; i<30; i++) //边界赋值
{
f[2][i]=1; //一直向右直走,情况为1
f[i][2]=1; //一直向下直走,情况为1
}
horse[hx][hy]=1; //标记马的点,不能走
horse[hx-2][hy-1]=1;
horse[hx-1][hy-2]=1;
horse[hx+1][hy+2]=1;
horse[hx+2][hy+1]=1;
horse[hx-1][hy+2]=1;
horse[hx+1][hy-2]=1;
horse[hx-2][hy+1]=1;
horse[hx+2][hy-1]=1;
f[1][2]=1; //到达起点前,可能是(1,2)或(2,1)到达(2,2),故步数设为1
for(int i=2;i<=bx;i++) //实际地图从(2,2)开始
for(int j=2;j<=by;j++)
{
if(horse[i][j]==1) //遇到障碍物马
{
f[i][j]=0; //归零
continue;
}
f[i][j]=f[i-1][j]+f[i][j-1]; //递推式
}
printf("%lld",f[bx][by]);
}
3.疯狂的采药
P1616 疯狂的采药 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)https://www.luogu.com.cn/problem/P1616
1.关键:完全背包问题,无限数量;2.正序:
for(i=1;i<=m;i++)
for(j=c[i];j<=t;j++)
dp[j]=max(dp[j],dp[j-c[i]]+w[i]);3.注意:数据类型 long long
#include <stdio.h>
long long c[20000000],w[20000000],dp[20000000];
long long max(long long a,long long b)
{
return a>b?a:b;
}
int main()
{
long long t,m,i,j;
scanf("%lld%lld",&t,&m);
for(i=1;i<=m;i++)
scanf("%lld%lld",&c[i],&w[i]); //c【】耗时,w【】价值
for(i=1;i<=m;i++)
for(j=c[i];j<=t;j++) //正序
dp[j]=max(dp[j],dp[j-c[i]]+w[i]);
printf("%lld",dp[t]);
return 0;
}