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

拿命运做标题其实是因为最广为人知吧,主要是总结这种 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

posted @ 2022-07-23 07:44  houzhiyuan  阅读(232)  评论(0编辑  收藏  举报