【典】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\) 就表示前面选了的区间的右端点最大值。
有了这个状态,转移方程就十分自然。
后面的需要满足 \([x,y]\) 是一个操作,这样可以直接前缀 \(\min\) 维护。
意思就是如果选一个区间,那么就是右端点和 \(y\) 取一个 \(\max\) 即可,一个区间在它的左端点选。
这样就可以得到一个 \(O(n^2)\) 的 dp,code。
考虑怎么优化这个式子。
其实也比较简单,用一个线段树动态维护,把第一维扔掉,那么第一个式子就是前缀,后缀加,第二个式子就可以直接前缀查 \(\min\),单点修改。
代码不是最短的,但写起来非常无脑。
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)\)。
给定一棵 \(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\),那么与状态没有啥关系,直接加和就好了。
否则考虑填 \(0\),那么此时状态第二维就要取两者较大值。
最后可以得到转移方程:
令 \(mx_i\) 表示限制中以 \(i\) 为下端点的,上端点深度最大值,那么初值就是 \(f_{i,mx_i}=1\)。
暴力代码可以看卡老师的,时间复杂度 \(O(n^2)\)。
考虑咋优化。
可以考虑线段树合并,每个节点开一棵线段树,分别表示状态,然后考虑状态咋合并。
第一个式子直接从左向右合并时维护和即可。
第二个就是一个 \(\max\) 卷积,也是直接维护前缀和。