背包问题复习
蓝桥杯下尽量不要使用min,cin
关于为什么01背包一维必须倒序,感谢我的队友一年不久给我解释清楚了
for(int i=1;i<=n;++i)
for(int j=1;j<=V;++j)
{
dp[j]=min(xxx,dp[j-price[i]]);
}
假如重量是1 2 2 3
那么dp[1]=1(已选用物品1) dp[2] 可以 在dp[1]的基础上选择物品1组成,即更新过程中,物品1又被利用了,意思是dp[2]是由两个dp[1]组成
注意不要忘记初始化
一、普通01背包
void solve
{
for(int i=1;i<=n;++i)
for(int j=0;j<=W;++j)
{
if(k<w[i])
dp[i][j]=dp[i-1][j];
else
dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);
}
}
优化后
void solve()
{
for(int i=1;i<=n;++i)
for(int j=W;j>=w[i];--j) 倒着的
dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
}
二、完全背包
void solve()
{
for(int i=1;i<=n;++i)
for(int j=0;j<=W;++j)
{
if(j<w[i])
dp[i][j]=dp[i-1][j];
else
dp[i][j]=max(dp[i-1][j],dp[i][j-w[i]]+v[i]); 注意对比和01背包的区别
}
}
优化后
void solve()
{
for(int i=1;i<=n;++i)
for(int j=w[i];j<=W;++j) w[i] 到 w
dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
}
三、多重背包
for(int i=1;i<=n;++i)
for(int j=W;j>=0;--j) 和01背包一致 是倒着的 W 到 0
for(int k=0;k<=num[i];++k)
{
if(j-k*w[i]<0) break;
dp[j]=max[j-k*w[i]]+k*v[i];
}
多重部分和问题 多重背包变种
有n种不同大小的数字a[i],每种有m[i]个,问能否选出他们中的一些刚好组成K
for(int i=1;i<=n;++i)
{
memset(cnt,0,sizeof(cnt)); //cnt记录的是用掉的第i种数的个数
for(int j=a[i];j<=W;++j)
{
if(!dp[j]&&dp[j-a[i]]&&cnt[j-a[i]]<c[i])
{
dp[j]=true;
cnt[j]=cnt[j-a[i]]+1;
}
}
}
四、子序列相关
最大连续子序列的起始和终止 dp[i]以i为结尾的最大连续子序列,当dp[i-1]>0的时候才加上前面
if(dp[i-1]<0) start=i; else dp[i]=dp[i-1]+a[i];
if(dp[i]>ans]) end=i; 就这样
最大上升子序列 dp[i]以i为结尾的最大上升子序列
for i=1:n
{
dp[i]=a[i]; 勿忘初始化
for j=1:i-1
if(a[j]<a[i]) dp[i]=max(dp[i],dp[j]+a[i]);
}
五、数字三角形问题&&多段图问题
数塔
要从下面往上算 dp[i][j]+=max(dp[i+1][j],dp[i+1][j+1]); 达到层层累加的效果
天上掉馅饼
dp[t][x]代表在时刻t,位置x处可以获得的馅饼数量
for(int i=T-1;i>=1;--i) 层 ------ 序
for(int j=0;j<11;++j) 状态+转移(决策)
dp[i][j]+=max(dp[i+1][j],dp[i+1][j-1],dp[i+1][j+1]); 从倒数第二层,逐次累加下一层,巧妙的思路
起点体现在最终输出答案dp[0][5]; 随层次的累加性
多段图也是这个样子,要从最后一列往前加 以前一直不明白,为什么第一维遍历是倒序,现在终于想清楚了
现在看看Spy in the metro 也是这个样子,序+状态+状态转转移 注意在dp过程中,起始位于哪里不重要。最后输出的时候用一下起点即可
dp[i][j] i时刻在j车站的最长还需要等待多少时间
for(int i=终可更新的状态;i>=起;i--) //序
for(从i到j能选择哪几步) //状态
{
处理每步的选择会对结果造成的影响 //决策
}
for(int i=1;i<=n-1;++i) dp[T][i]=INF;
dp[T][n]=0;
for(int i=T-1;i>=0;--i)
for(int j=1;j<=n;++j)
{
dp[i][j]=dp[i+1][j]+1;
if(j<n&&HasTrain[i][j][0]&&i+t[j]<=T)
dp[i][j]=min(dp[i][j],dp[i+t[j]][j+1]);
if(j>1&&HasTrain[i][j][1]&&i+t[j-1]<=T)
dp[i][j]=min(dp[i][j],dp[i+t[j-1]][j-1]);
}
cout<<"Case Number "<<kase<<": ";
if(dp[0][1]>=INF) cout<<"impossible\n"; //事实上结果也有可能dp[0][1]>INF一直在等待
else
cout<<dp[0][1]<<endl;
六、丑数扩展
l1,l2,l3分别为p1,p2,p3的指针 比priority_queue好用
for(int i=1;i<=idx;++i)
{
dp[i]=min(dp[l1]*p1,dp[l2]*p2,dp[l3]*p3);
if(!(dp[i]%p1)) l1++;
if(!(dp[i]%p2)) l2++;
if(!(dp[i]%p3)) l3++;
}
七、DAG上的dp
求问它和最短路啥关系?
dp[i]=max(dp[j]+v[i]); (i,j)\in E 以i为起点的最长路
int dp(i)
{
int &ans=d[i];
if(ans>0) return ans;
ans=v[i]; //它自身的高
for(int j=0;j<3*n;++j)
{
if(G[i][j])
d[i]=max(ans,dp(j)+v[i]);
}
return ans;
}
for(int i=0;i<3*n;++i)
ans=max(ans,d[i]);
八、硬币问题
凑成S元最少需要多少硬币和最多需要多少硬币
最大值:
int dp(int s) 记忆化搜索
{
if(vis[s]) return d[s];
vis[s]=true;
int &ans=d[s];
for(int i=1;i<=n;++i) if(s>v[i]) ans=max(ans,dp(s-v[i])+1); 硬币数最大
return ans;
}
for(int i=1;i<=s;++i) 递推法 外层循环是迭代传值,内层循环还是内层循环
for(int j=1;j<=n;++j)
if(i>=v[j])
minv[i]=min(minv[i],minv[i-v[j]]+1); maxv相同
电源替换 还是觉得难以真正的理解
int solve(int u)
{
if(vis[u]) return dp[u];
vis[u]=1;
int &ans=dp[u];
if(u==0) return 0;
ans=INF:
sum=a[u].k;
for(int i=u;i;--i)
{
灯泡u全部替换的价格
ans=min(ans,sum+solve(i-1)); 不是ans=min(ans,sum+solve(u-1));
}
}
九、01背包变种
1、金明的预算方案
for(i=1;i<=m;++i)
for(j=1;j<=n;++j)
{
if(j-w[i][0]>=0) //主件可以买
{
dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i][0]]+v[i][0]); //买不买主件
if(j-w[i][0]-w[i][1]>=0) //主件+附件1
dp[i][j]=max(dp[i][j],dp[i-1][j-w[i][0]-w[i][1]]+v[i][0]+v[i][1]);
if(j-w[i][0]-w[i][2]>=0) //主件+附件2
dp[i][j]=max(dp[i][j],dp[i-1][j-w[i][0]-w[i][2]]+v[i][0]+v[i][2]);
if(j-w[i][0]-w[i][1]-w[i][2]>=0) //主件+附件1+附件2
dp[i][j]=max(dp[i][j],dp[i-1][j-w[i][0]-w[i][1]-w[i][2]]+v[i][0]+v[i][1]+v[i][2]);
}
else
dp[i][j]=dp[i-1][j];
}
特殊的题:求最大值 (ai,bi) 选几对要求和最大,并且sigma A sigma B都不能小于0 思路:考虑前i种的情况下,固定a的值,求b的最大值 amazing~
/dp[i][j]考虑前i对,在a[i]和为j的情况下能得到的最大b值
以及区间由负数的时候把它+区间长度
2、要求被选中的种数最多,在此基础上价值最大
方法:定义dp[j]为时间j,最多能刚好唱完多少首歌。除了dp[0]=0以外,其他都无法转移过去,求歌曲数目最大值,那么dp[j] j!=0初始化为-INF(保证无法转移过去)
dp[j]=max(dp[j],dp[j-w[i]]+1);
for(int j=t-1;j>=0;--j) 然后倒着找
{
if(ans<dp[j])
{
ans=dp[j];
t=j+678;
}
}
3、概率01背包
概率01背包
dp[j]偷j的钱最大的逃跑概率 dp[j]=max(dp[j],dp[j-v[i]]*(1-p[i]));
for(int i=1;i<=n;++i)
for(int j=W;j>=v[i];--j)
dp[j]=max(dp[j],dp[j-v[i]]*(1-p[i]));
for(int j=money;j>=0;--j)
{
if(dp[j]>1-P) ans=j,break;
}
dp[j]花了i钱没学上的概率 dp[j]=min(dp[j],dp[j-v[i]]*(1-p[i]));
最优矩阵链乘+四边形优化
for(int len=2;len<=n;++len)
for(int i=1;i+len-1<=n;++i)
{
int j=i+len-1;
for(int k=p[i][j-1];k<=p[i+1][j];++k) 这行总是忘记
{
int tmp=dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1];
if(tmp<dp[i][j])
{
dp[i][j]=tmp;
p[i][j]=k;
}
}
}