ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

【典】P6773 和区间有关的 dp 的一种简单状态+线段树(合并)维护dp

2022-07-23 08:01:08  阅读:92  来源: 互联网

标签:sum 最大值 dp 端点 区间 线段 P6773


拿命运做标题其实是因为最广为人知吧,主要是总结这种 dp 设计方法以及线段树维护的巧妙之处。

已知一个序列,有 \(Q\) 个区间修改(限制条件),求有多少方案数(最小最大值)。

这种区间的限制一般比较阴间,没有那么简单。

这里要介绍的一种方法就是列状态 \(f_{i,j}\) 表示 \([1,i]\) 已经填好,接下来对 \(i\) 产生影响的区间的右端点的最大值(最小值)为 \(j\),在区间的加持下,一般可以用线段树进行优化。

这种做法的好处是转移很简单,而且思维难度较低,线段树优化一般也比较显然,不需要对复杂的区间关系进行处理。

先来个简单题。


ARC085F *3094

有一个长度为 \(n\) 的序列 \(b\),现在有一个初始全部是 \(0\) 的序列 \(a\),有 \(Q\) 种操作,每种操作可以把一个区间 \([l_i,r_i]\) 全部变成 \(1\),定义一个序列的价值为 \(\sum |a_i-b_i|\),求若干次操作后的最小价值。

\(n\le 2\times 10^5\)。

纵观洛谷题解和 AT 题解,都可以发现做法要么把区间设进状态里,要么对区间关系进行分类讨论,或者进行复杂转化,总之,思维难度大,代码也不是很好写。

来一个无脑解法。

设 \(f_{i,j}\) 表示填完前 \(i\) 个了,那么考虑前面的区间会对后面造成的贡献是什么,自然就是覆盖为 \(1\) 的区间的右端点,因此 \(j\) 就表示前面选了的区间的右端点最大值。

有了这个状态,转移方程就十分自然。

\[f_{i,j}=\begin{cases}f_{i-1,j}+b_i&j<i\\f_{i-1,j}+1-b_i&j\ge i\end{cases}\\ f_{x,y}=1-b_{x}+\min_{j<y} f_{x-1,j} \]

后面的需要满足 \([x,y]\) 是一个操作,这样可以直接前缀 \(\min\) 维护。

意思就是如果选一个区间,那么就是右端点和 \(y\) 取一个 \(\max\) 即可,一个区间在它的左端点选。

这样就可以得到一个 \(O(n^2)\) 的 dp,code

考虑怎么优化这个式子。

其实也比较简单,用一个线段树动态维护,把第一维扔掉,那么第一个式子就是前缀,后缀加,第二个式子就可以直接前缀查 \(\min\),单点修改。

code

代码不是最短的,但写起来非常无脑。


CF930E *2900

一个长度为 \(k\) 的 \(01\) 串,给出 \(n+m\) 个约束条件,其中 \(n\)条描述区间 \([l_i,r_i]\) 至少有一个 \(0\),其中 \(m\) 条描述区间 \([l_i,r_i]\) 至少有一个 \(1\) 。求合法的 \(01\) 串数量,答案对 \(10^9+7\) 取模。
\(k\le 10^9,n,m\le 10^5\)。

标签:dp,线段树。

和上面一题差不多,也是直接用 dp 式子。

先设 \(f_{i,j,k}\) 表示 \(1...i\) 填完,左端点在 \(i\) 左边,没有被满足的 \(0\) 限制右端点最小是 \(j\),\(1\) 限制是 \(1\) 的是 \(k\) 的方案数。

然后发现 \(i\) 要么填 \(1\) 要么填 \(0\),所以必然有一个限制可以扔掉,所以状态变成 \(f_{i,j,0/1}\)。

然后转移就很简单,直接讨论 \(i+1\) 填什么即可,可以看 暴力代码,时间复杂度 \(O(k^2)\)。

然后发现这个东西可以用线段树优化,分析一下转移方程可以发现是一个后缀加到一个点上以及区间清空,如果后面没有限制 \(j\) 就给个最大值,所以可以直接维护,时间复杂度 \(O(k\log k)\)。

然后发现可以离散化,中间状态就是满足至少有一个 \(0\) 或者 \(1\) 去转移,也就是$ 2^s-1\(,\)s$ 为区间长度,时间复杂度 \(O(n\log n)\)。

code


P6773

给定一棵 \(n\) 个点的树和 \(m\) 条限制,你可以给树上的每一条边赋一个 \(0\) 或 \(1\) 的权值。对于所有限制 \((u,v)\) (保证 \(v\) 为 \(u\) 的祖先) 你需要保证 \(u\) 到 \(v\) 上至少有一条边的权值为 \(1\),求赋值方案数。
\(n,m\le 5\times 10^5\)

考虑暴力 dp 咋做,本质上就是上面题的树上版本。

依然用前面的状态 \(f_{i,j}\) 表示在 \(i\) 子树中所有下端点限制中,没有满足的上端点的深度最大值是 \(j\),\(i\) 子树中的填边方案,因为如果有多个不满足条件的上端点,显然取最下面哪一个,因为最下面的满足了上面的也一定满足,如果 \(j=0\),则表示条件都满足。

考虑咋转移。

如果 \(u\) 的儿子是 \(v\),那么如果 \((u,v)\) 填 \(1\),那么与状态没有啥关系,直接加和就好了。

\[f_{u,i}=\sum_{j=0}^{dep_u}f_{u,i}f_{v,j} \]

否则考虑填 \(0\),那么此时状态第二维就要取两者较大值。

\[f_{u,i}=\sum_{j=0}^i f_{u,i}f_{v,j}+\sum_{j=0}^{i-1} f_{u,j}f_{v,i} \]

最后可以得到转移方程:

\[f_{u,i}=f_{u,i}(\sum_{j=0}^{dep_u}f_{v,j}+\sum_{j=0}^i f_{v,j})+f_{v,i}(\sum_{j=0}^{i-1} f_{u,j}) \]

令 \(mx_i\) 表示限制中以 \(i\) 为下端点的,上端点深度最大值,那么初值就是 \(f_{i,mx_i}=1\)。

暴力代码可以看卡老师的,时间复杂度 \(O(n^2)\)。

考虑咋优化。

可以考虑线段树合并,每个节点开一棵线段树,分别表示状态,然后考虑状态咋合并。

第一个式子直接从左向右合并时维护和即可。

第二个就是一个 \(\max\) 卷积,也是直接维护前缀和。

code

标签:sum,最大值,dp,端点,区间,线段,P6773
来源: https://www.cnblogs.com/houzhiyuan/p/16508456.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有