动态规划(冬令营课堂笔记)
简单问题
01背包
一个容量为m的背包,有n个物品,第i个物品的体积为wi,价值为ci。选择若干物品,使得体积总和不超过m的情况下价值总和最大。
n<=100,m<=10000。
搜索 复杂度为2^n
void dfs(int x,int y,int z)//当前物品编号,当前容量,当前价值 { if(x==n+1) { if(y<m)ans=max(ans,z); return; } dfs(x+1,y+w[x],z+c[i]); dfs(x+1,y,z); }
如果前两维相同,只需选择第三维的最大值
小小的变形
const int INF=0x7f; int dfs(int x,int y) { int ans; if(x==n+1) { if(y>m)return -INF; return 0; } ans=max(dfs(x+1,y+w[x])+c[x],dfs(x+1,y)); return ans; } dfs(1,0);
加一个数组记录
const int INF=0x7f; int dfs(int x,int y) { if(x==n+1) { if(y>m)return -INF; return 0; } dp[x][y]=max(dfs(x+1,y+w[x])+c[x],dfs(x+1,y)); return dp[x][y]; } dfs(1,0);
应用记录状态 记忆化搜索 n*m
const int INF=0x7f; int dfs(int x,int y) { if(y>m)return -INF; if(x==n+1) { return 0; } if(dp[x+1][y+w[x]]) //选该物品 dp[x][y]=max(dp[x+1][y+w[x]]+c[x],dp[x][y]); else dp[x][y]=max(dfs(x+1,y+w[x])+c[x],dp[x][y]); if(dp[x+1][y]) //不选该物品 dp[x][y]=max(dp[x+1][y]+c[x],dp[x][y]); else dp[x][y]=max(dfs(x+1,y))+c[x],dp[x][y]); return dp[x][y]; } dfs(1,0);
递归变递推
先写出搜索
最大/最小值 存放到数组中
后面递归该状态时 直接使用数组中的值来代替
O(dfs里的状态个数*转移的时间复杂度)
找边界
找递推方法(顺序还是逆序)
转移(推转移方程)
搜索->记忆化搜索->递归变递推
for(int i=n;i>=1;i--) for(int j=0;j<=m;j++) dp[i][j]=max(dp[i+1][j],dp[i+1][j+w[i]]+c[i]); ans=dp[1][0]
012背包
一个容量为m的背包,有n个物品,第i个物品的体积为wi,价值为ci,有2个。
选择若干物品,使得体积总和不超过m的情况下价值总和最大。
记忆化搜索
const int INF=0x7f; int dfs(int x,int y,int z) { if(x==n+1){if(y<m)ans=max(ans,z);return -INF;} if(dp[x+1][y]) //一个都不选 dp[x][y]=max(dp[x][y],dp[x+1][y]); else dp[x][y]=max(dp[x][y],dfs(x+1,y)); if(dp[x+1][y+w[x]]) //选一个 dp[x][y]=max(dp[x][y],dp[x+1][y+w[x]])+c[x]); else dp[x][y]=max(dp[x][y],dfs(x+1,y+w[x])+c[x]); if(dp[x+1][y+w[x]]) //选两个 dp[x][y]=max(dp[x][y],dp[x+1][y+2*w[x]])+2*c[x]); else dp[x][y]=max(dp[x][y],dfs(x+1,y+2*w[x])+2*c[x]); return dp[x][y]; } dfs(1,0);
部分背包
一个容量为m的背包,有n个物品,第i个物品的体积为wi,价值为ci,有ki个。
选择若干物品,使得体积总和不超过m的情况下价值总和最大。
记忆化搜索
int dfs(int x,int y) { if (y>m) return -INF; if (x==n+1) return 0; for (int i=0; i<=k[x]; i++) { if (dp[x+1][y+i*w[x]]) dp[x][y]=max(dp[x][y],dp[x+1][y+i*w[x]]+c[x]); else dp[x][y]=max(dp[x][y],dfs(x+1,y+i*w[x])+i*c[x]); } return dp[x][y]; } dfs(1,0);
递推
for (i=n; i>=1; i--) for (j=0; j<=m; j++) for (l=0; l<=k[i]; l++) dp[i][j]=max(dp[i][j],dp[i+1][j+l*w[i]]+l*c[i]); dp[1][0]
机器分配
有n家店,每家店都有m台机器,第i家店购买j台机器花费a[i][j]元(可能存在a[i][j]>a[i][j+1]),要购买总共m台机器,求最小花费。
搜索
//机器分配 dfs(int x,int y,int z) { if(y>m)return; if(x==n+1) { if(y==n+1)ans=min(ans,z); return; } for(int i=0;i<=m;i++) dfs(x+1;y+i,z+a[x][i]); } dfs(1,0,0);
小小的变形
//机器分配 int dfs(int x,int y) { if(y>m)return -INF; if(x==n+1) { if(y==m)return 0;//再也不需要买机器 return -INF; } for(int i=0;i<=m;i++) dp[x][y]=min(dp[x][y],dfs(x+1,y+i)+a[x][i]); return dp[x][y]; } dfs(1,0);
记忆化搜索
//机器分配 int dfs(int x,int y) { if(y>m)return -INF; if(x==n+1) { if(y==m)return 0;//再也不需要买机器 return -INF; } for(int i=0;i<=m;i++) if(dp[x+1][y+i]) dp[x][y]=min(dp[x][y],dp[x+1][y+i]+a[x][i]); else dp[x][y]=min(dp[x][y],dfs(x+1,y+i)+a[x][i]); return dp[x][y]; } dfs(1,0);
递推
for(int i=n;i>=1;i--) for(int j=0;j<=m;j++) { dp[i][j]=INF; for(k=0;k<=m-j;k++) dp[i][j]=min(dp[i][j],dp[i+1][j+k]+a[i][k]); } ans=dp[1][0]
烽火传递
给定n个非负整数,选择其中若干数字,使得每连续k个数中至少有一个数被选出。
要求选择的数字之和尽可能小。
搜索
void dfs(int x,int y) { if(x==n+1){ans=min(ans,y);return;} for(int i=x+1;i<=min(n+1,x+k);i++) dfs(i,y+a[i]);//i是下一个 } dfs(0,0);
记忆化
int dfs(int x) { if(x==n+1)return 0; dp[x]=INF; for(int i=x+1;i<=min(n+1,x+k);i++) if(dp[i])dp[x]=min(dp[x],dp[i]+a[i]); else dp[x]=min(dp[x],dfs(i)+a[i]); return dp[x]; } dfs(0);
递推
/*dp[n+1]=0; dp[1] dp[2] dp[3] .. dp[1+k]*/ for (i=n; i>=0; i--) { dp[i]=INF; for (int j=i+1; j<=min(n+1,i+k); j++) dp[i]=min(dp[i],dp[j]+a[j]); } dp[0]
花店橱窗问题
给定一个n*m的矩阵A(n<=m),求一个序列a1,a2,…,an满足1<=a1<a2<…<an<=m。使得A[1][a1]+A[2][a2]+…+A[n][an]最大。A可能有负数。
搜索
void dfs(int x,int y,int z) { if(x==n+1) { if(y==m+1)ans=max(ans,z); return; } for(int i=y+1;i<=m;i++) dfs(x+1,i,z+a[x+1][i]); } dfs(0,0,0);
记忆化搜索
int dfs(int x,int y) { if(x==n+1) { if(y==m+1)return 0; else return -INF; } for(int i=y+1;i<=m;i++) { if(dp[x+1][i]) dp[x][y]=max(dp[x][y],dp[x+1][i]+a[x+1][i]); else dp[x][y]=max(dp[x][y],dfs(x+1,i)+a[x+1][i]); return dp[x][y]; } } dfs(0,0);
递推
/*dp[n+1][m+1]=0; dp[n+1][0~m]=-INF; dp[1][] dp[2][] dp[3][]*/ for (i=n; i>=0;i--) for (j=0; j<=m+1; j++) for (k=j+1; k<=m+1; k++) dp[i][j]=max(dp[i][j],dp[i+1][k]+a[i+1][k]); cout<<dp[0][0];