算法作业——5.6
问题一
John 有两个孩子,在 John病逝后,留下了一组价值不一定相同的魔卡, 现在要求你设计一种策略,帮John的经管人将John的这些遗产分给他的两个孩子,使得他们获得的遗产差异最小(每张魔卡不能分拆)。
解决思路
解法一
暴力枚举。总共 nnn 个魔卡,那么我们算出每一种选择下两个孩子的遗产差异即可。共 2n2^n2n 种情况, 可利用dfs完成搜索并更新答案。
时间复杂度 O(2n)O(2^n)O(2n)
解法二
假设魔卡总价值为 mmm ,那么当遗产分配完只有两种情况:1.两个孩子正好各 m2\frac m22m 2.一个孩子大于 m2\frac m22m ,另一个则小于。假设第一个孩子得到的遗产总 value≤m2value\leq \frac m2value≤2m ,那么只要在此限制下尽量多得到魔卡。
那么这个问题即变为了一个01背包问题,且每个物品的价值和体积均为魔卡的价值。设 fi,jf_{i,j}fi,j 表示在前 iii 件物品中选择,背包容量为 jjj 时可获得的最大价值,则转移方程为
fi,j=max{fi−1,j,fi−1,j−wi+wi}
f_{i,j}=\max\{f_{i-1,j},f_{i-1,j-w_i}+w_i\}
fi,j=max{fi−1,j,fi−1,j−wi+wi}
void solu{
for(int i=1;i<=n;i++)
for(int j=m/2; j>=w[i];j--){
f[i][j]=max(f[i-1][j],f[i-1][j-w[i]]+w[i])
num[j]=i; // 记录对应选定的魔卡序号
}
}
背包问题也可以对空间复杂度进行优化,即使用滚动数组。原理为每次转移只用到了第 iii 和 i−1i-1i−1 层,则每次更新覆盖即可,为了防止先更新了较小的 jjj ,影响后面的转移,所以 jjj 从倒序转移。
void solu{
for(int i=1;i<=n;i++)
for(int j=m/2; j>=w[i];j--)
if(f[i-1][j]<f[i-1][j-w[i]]+w[i]){
f[i][j]=f[j-w[i]]+w[i];
num[j]=i; // 记录对应选定的魔卡序号
}
}
问题二
假设已知某股票连续若干天的股价,并且如何时候你手上只能由一支股票,即如果你要买入就得先将手上股票卖出,设计一个算法来计算你所能获取的最大利润。你最多可以完成 k笔交易。也就是说,你最多可以买k 次,卖 k 次。
解决思路
解法一
问题是最优问题,但不满足最优子问题,考虑动态规划。
设 fi,j,0/1f_{i,j,0/1}fi,j,0/1 表示到第 iii 天,已经买入了 jjj 次,手中有 0/10/10/1 支股票时的最大收益,则分三种情况:1.没有股票这一天买入 2.有股票这一天卖出 3.啥也不干
得到转移方程
fi,j,1=max{fi−1,j−1,0−pi}fi,j,0=max{fi−1,j,1+pi}fi,j,0/1=maxfi−1,j,0/1
f_{i,j,1}=\max\{f_{i-1,j-1,0}-p_i\}\\
f_{i,j,0}=\max\{f_{i-1,j,1}+p_i\}\\
f_{i,j,0/1}=\max f_{i-1,j,0/1}
fi,j,1=max{fi−1,j−1,0−pi}fi,j,0=max{fi−1,j,1+pi}fi,j,0/1=maxfi−1,j,0/1
void solu{
for(int i=1;i<=k;i++)
f[1][i][1]=-p[0];
for(int i=2;i<=n;i++){
for(int j=1;j<=k;j++){
if(j==1) f[i][j][1]=max(f[i-1][j][1],-p[i]);
f[i][j][1]=max(f[i-1][j][1],f[i-1][j-1][0]-p[i]);
f[i][j][0]=max(f[i-1][j][0],f[i-1][j][1]+p[i]);
}
}
cout<<f[n][k][0];
}
可以空间优化,同样只用到了第 iii 和 i−1i-1i−1 层,则每次更新覆盖即可。为了防止先更新了较小的 jjj ,影响后面的转移,所以 jjj 从倒序转移。
void solu{
for(int i=1;i<=k;i++)
f[i][1]=-p[0];
for(int i=2;i<=n;i++){
for(int j=k;j>=1;j--){
if(j==1) f[j][1]=max(f[j][1],-p[i]);
f[j][1]=max(f[j][1],f[j-1][0]-p[i]);
f[j][0]=max(f[j][0],f[j][1]+p[i]);
}
}
cout<<f[n][k][0];
}
时间复杂度为 O(nk)O(nk)O(nk) ,空间复杂度为 O(n)O(n)O(n)
解法二
wqs二分——给定 nnn 个物品,我们需要在其中 恰好 选择 kkk 个,并且需要最大化收益。设对应的收益为 gkg_kgk ,那么需要满足 在最大化收益的前提下,每多选择一个物品,额外产生的收益是单调递减的,也就是 gk+1−gk≤gk−gk−1g_{k+1}-g_k\leq g_k-g_{k-1}gk+1−gk≤gk−gk−1 。同时,如果我们 对物品的选择数量没有限制,即 kkk 不存在,那么我们应当能够快速地计算出最大的收益,以及达到最大的收益需要选择的物品数量。
我们设恰好完成 kkk 笔交易时,能够获取的最大收益为 gkg_kgk ,那么
gk+1−gk≤gk−gk−1
g_{k+1}-g_k\leq g_k-g_{k-1}
gk+1−gk≤gk−gk−1
是成立的。我们可以这样想:我们每额外增加一笔交易 gk→gk+1g_k \rightarrow g_{k+1}gk→gk+1 ,那么这一笔交易一定不会比上一笔交易 gk−1→gkg_{k-1} \rightarrow g_{k}gk−1→gk 产生的收益高,否则我们就可以交换这两笔交易,使得 gkg_kgk 更大,那么就与 gkg_kgk 是恰好完成 kkk 笔交易时的最大收益这个事实相矛盾了。
如果我们把 (k,gk)(k,g_k)(k,gk) 看成平面直角坐标系上的点,那么这些点就组成了一个上凸壳。
虽然我们并不知道 gkg_kgk 的值到底是多少(否则我们就可以直接返回正确答案了),但是我们知道 (k,gk)(k,g_k)(k,gk) 对应的图像的形状,且以 (k,gk)(k,g_k)(k,gk) 与 (k+1,gk+1)(k+1,g_{k+1})(k+1,gk+1) 为端点的线段的 斜率是单调递减的。则可以通过对 斜率进行二分,求出 gkg_kgk 的值。
如果我们选择了一个合适的斜率 c′c'c′ ,使得其与上凸壳相切在了某一个我们需要的 (k,gk)(k,g_k)(k,gk) 的位置,这样我们就可以在 O(n)O(n)O(n) 的时间内直接求出不限制交易次数的最大收益,并且我们知道它实际上就是交易了 kkk 次。
(因为斜率 ccc 且与上凸壳相切的直线的截距为 gk−k⋅cg_k-k\cdot cgk−k⋅c ,这个价值就是含手续费为 ccc 的无限制买卖,且恰好交易 kkk 次)
但其实还有两个问题
1.本题中我们限制的是最多进行 kkk 次交易,而不是恰好进行 kkk 次交易
如果 (k,gk)(k,g_k)(k,gk) 所在的位置是上凸壳的左半部分(即斜率大于等于 000 的部分),那么我们就可以使用上面的方法得到答案,这是因为最优的答案一定是进行 kkk 次交易的;
如果 (k,gk)(k,g_k)(k,gk) 所在的位置是上凸壳的右半部分(即斜率小于 000 的部分),那么我们通过二分是没有办法找到斜率 ccc 并且计算出对应的 (k,gk)(k,g_k)(k,gk) 的。具体的做法可以在第二个问题中得到解释,也就是我们可以规定二分查找的上下界。
2.二分上下界如何确定
令下界为1,则右半部分会被舍去,若 (k,gk)(k,g_k)(k,gk) 的位置在右半部分,则查找失败。
令上界为最大的股价 PPP ,如果查找失败,那么说明最大收益对应的交易次数是严格小于题目中给定的 kkk 的,这就说明 交易次数的限制并不是瓶颈,而价格才是 ,那就可以当作无限交易次数,利用贪心解决
ps:我们二分查找判断的收益是 gk−k⋅cg_k-k\cdot cgk−k⋅c ,要在结果上加上 k⋅ck\cdot ck⋅c
void solu{
for(int i=1;i<=n;i++)
P=max(P,price[i]);
int l=1,r=P;
int ans=-1;
while(l<=r){
int c=(l+r)>>1;
//无限制的贪心
int buy_cnt=0,sell_cnt=0;
int buy=-price[0],sell=0;
for(int i=1;i<=n;i++){
if(sell-price[i]>=buy){
buy=sell -price[i];
but_cnt=sell_cnt;
}
if(buy+price[i]-c>=sell){
sell=buy+price[i]-c;
sell_cnt=buy_cnt+1;
}
}
if(sell_cnt>=k){
ans=sell+k*c;
l=c+1;
}
else r=c-1;
}
//如果二分查找失败
if(ans==-1){
ans=0;
for(int i=1;i<n;i++)
ans+=max(price[i]-price[i-1],0);
}
cout<<ans;
}
时间复杂度 O(nlogP)O(n\log P)O(nlogP) ,空间复杂度 O(1)O(1)O(1)