单调队列优化DP

全局最优解必然包含局部最优解,因此每次转移只需考虑局部最优解!!!

主要内容

形如这样\(\operatorname{DP}\) 转移方程:

\[dp[i]=\max_{L_i\le j\le R_i}{\{dp[i]+val(i,j)\}} \]

满足:

  1. \(\{L_i\}\) , \(\{R_i\}\) 递增( 前提条件 )。

  2. \(R_i \le i\) ( 转移条件 )。

  3. \(val(i,j)\) 值只与 \(j\) 相关 ( 根本优化转移前提 ) 。

维护一个滑动窗口,每次求窗口中的最大值。对于两个点 \(x\)\(y\) ,如果 \(x < y\)\(f(x) < f(y)\) ,那么 \(y\) 进入窗口后,决策点一定不会是 \(x\)

用一个单调队列维护窗口里所有可能用到的决策点。

窗口右端点向右滑动时,把一个新的点插入队尾。队尾点为 \(q[r]\) ,新点为 \(x\) ,如果 \(f(q[r]) \le f(x)\) ,那么 \(q[r]\) 没用,把 \(q[r]\) 弹掉。重复过程直到队尾点可能有用,即 \(f(q[r]) > f(x)\) ,把 \(x\) 入队。

队列中的 \(f(i)\) 从队首到队尾递减。决策时,首先弹掉队首超过范围的点。这时队首点就是决策点。\(f(i) = \max {\{f(j) + w_i\}} [i-R_i \le j \le i-L_i]\)

单调队列优化 也称为 滑动窗口


变式 \(-\) 单调队列优化多重背包

内容

\(dp[i][j]\) 表示前 \(i\) 个物品放入容量为 \(j\) 的背包的最大收益 。

\[dp[i][j]=\max_{k=0}^{k\le k[i]}{\{dp[i-1][j-k\times c[i]]+k\times w[i]\}} \]

考虑 \(dp\) 的转移 。

\[0\le p < c[i],0\le j \le \left\lfloor \dfrac{V-p}{c[i]}\right\rfloor,0\le k \le k[i] \]
\[dp[i][p+j\times c]=\max{\{dp[i-1][p+(j-k)\times c]+k\times w\}} \]
\[dp[i][p+j\times c]=\max{\{dp[i-1][p+(j-k)\times c]-(j-k)\times w+j\times w\}} \]
\[dp[i][p+j\times c]=\max{\{dp[i-1][p+(j-k)\times c]-(j-k)\times w\}}+j\times w \]

这样就可以进行单调队列优化了 。

时间复杂度:\(O(nV)\)

核心代码:( P1776 宝物筛选 ) 代码中的 \(pos\) 就是上面的 \(j-k\)

int ql,qr;
struct QUE
{
	 int num,val;
}que[Maxv];
void many_pack(int c,int w,int m)
{
	 if(!c) { add+=m*w; return; }
	 m=min(m,V/c);
	 for(int pos=0,s;pos<c;pos++)
	 {
	 	 ql=1,qr=0,s=(V-pos)/c;
	 	 for(int j=0;j<=s;j++)
	 	 {
	 	 	 while(ql<=qr && que[qr].val<=(dp[pos+j*c]-j*w)) qr--;
	 	 	 que[++qr]=(QUE){j,dp[pos+j*c]-j*w};
	 	 	 while(ql<=qr && (j-que[ql].num)>m) ql++;
	 	 	 dp[pos+j*c]=max(dp[pos+j*c],que[ql].val+j*w);
		 }
	 }
}

多重背包的其他解法:二进制分组优化 ,时间复杂度: \(O(V\sum_{i=1}^{n}\log_2{k_i})\) ,见背包问题

注意:

用结构体存储单调队列,防止反复修改 \(dp\) 值。

并注意 \(j=0\) 时的情况,及时更新。


二维单调队列

对于每一列维护一个竖直方向上的一维单调队列。

在每一行统计答案的时候,用一个新的一维单调队列维护每一列的最优答案。

最终答案在新的一维单调队列上。

例题:P2219 [HAOI2007]修筑绿化带


例题

P1725 琪露诺

$\texttt{solution}$

状态:设 \(dp[i]\) 表示走到 \(i\) 的最大收益。

\(L\)\(R\) 都是上文中的转移范围。

核心代码:

n=rd(),tmpl=rd(),tmpr=rd();
for(int i=0;i<=n;i++) a[i]=rd();
for(int i=1;i<=n;i++) L[i]=i-tmpr,R[i]=i-tmpl;
memset(dp,-inf,sizeof(dp));
dp[0]=a[0];
for(int i=1;i<=n;i++) // 必须从 l 开始 
{
	 if(R[i]<0) continue;
	 while(l<=r && q[l]<L[i]) l++;
	 while(l<=r && dp[q[r]]<=dp[R[i]]) r--;
	 q[++r]=R[i]; // 因为 i-L 小于 i ,所以应该确保最有决策再进行转移 
	 dp[i]=dp[q[l]]+a[i];
}
int ans=-inf;
for(int i=L[n]+1;i<=n;i++) ans=max(ans,dp[i]);
printf("%d\n",ans);

P3572 [POI2014]PTA

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值