DP 优化

DP优化

一些典型或者不典型的 DP 优化实例。

CF354D Transferring Pyramid

题意:有一个 \(n\) 层的金字塔,现在能进行两种操作,一是给某个点染色,代价是 3,或者画一个底边是金字塔底边的子三角形,给每个点,代价是点数 +2,要求用最小代价覆盖所有要染色的 \(m\) 个点。

思路:定义 \(h_i\) 表示点 \(i\) 到底边的距离,那么要用第二种方法覆盖这个点的代价是 \(O(h_i^2)\) 的,因此只有 \(h_i\leqslant\sqrt{m}\) 的点才有可能用第二种方法覆盖。考虑沿着对角线从左下到右上进行 dp,维护操作二的轮廓线高度,即设 \(f(i,j)\) 表示转移到第 \(i\) 条对角线,轮廓线最高高度为 \(j\) 的最小代价。转移时有延续之前的轮廓线或者让之前的轮廓线断开两种,再加上 \(h_i>\sqrt{m}\) 的即可。

复杂度 \(O(n\sqrt m)\)

trick:根据题目中的条件分析实际有用的范围,优化状态量。

P3506 [POI2010]MOT-Monotonicity 2

题意:给定长为 \(n\) 的序列 \(a\) 和长为 \(k\) \(s\),要求选出一个长度最长的子序列 \(p_1,p_2\cdots p_k\),使得 \(p_i\)\(p_{i+1}\) 的关系是 \(s[(i-1)\bmod k+1]\)

思路:一开始的想法有点接近正解,不过还是差不少。首先,可以大胆猜想,以 \(i\) 为结尾的最长合法子序列一定是由某个以 \(j(j<i)\) 结尾的最长合法子序列后面加上 \(i\) 得到的。我们来证明一下。

\(f(i)\) 表示 \(i\) 结尾的最长合法子序列长度。首先,假设以 \(n\) 结尾的最长合法子序列不是由一个位置的最长合法子序列得来的,设这个子序列的上一个是 \(m\),那么首先有 \(f(n)-1<f(m),f(n)\leqslant f(m)\)。假设有 \(a_m=a_n\),那么显然可以直接在 \(f(m)-1\) 后面接 n,这样不劣;假设 \(a_m<a_n\),再分 \(a_{f(m)-1}<a_n\)\(a_{f(m)-1}\geqslant a_n\) 两种。对于前者,直接在后面把 \(n\) 接上即可;对于后者,一定存在一个 \(w\geqslant f(n)\) 满足关系符号为 \(>\),那么把 \(n\) 接在第 \(w+1\) 个位置,一定更优。于是就证完了。

既然证明了满足最优子结构,就说明每次可以选择合法的最大答案,可以直接用树状数组维护。

trick:最优子结构的体现。

P6564 [POI2007] 堆积木KLO

题意:给定序列 \(a\),可以任意删除一些数,删除后后面的位置会向前补齐,求最后序列中最多有多少个数满足 \(a_i=i\)

思路:我们记 \(f_i\) 表示钦定最后 \(i\) 在自己位置上时 \(i\) 前最多有多少个满足条件的数,然后可得:\(f_i=\max(f_j|j<i,a_j<a_i,a_i-a_j\leqslant i-j)+1\)。朴素转移是 \(O(n^2)\) 的,我们尝试把合法的 \(j\) 变成一个前缀的形式。由 \(a_i-a_j\leqslant i-j\) 可以推出 \(j-a_j\leqslant i-a_i\),因此先排除不合法(即 \(a_i>i\) )的数,再按 \(i-a_i\) 排序后DP,然后用树状数组维护最大值即可。复杂度 \(O(n\log(n))\)

trick:分析出转移顺序,再按照指定顺序转移会更方便。

P4099 [HEOI2013]SAO

题意:给定一颗树,遍历方向为根到叶子或叶子到根,求遍历方案数。

思路:看到求拓扑序方案数首先想到设 \(f[i][j]\) 表示 \(i\)\(i\) 的子树里拓扑序排名为 \(j\) 的方案数,然后考虑合并时如何转移。\(f[x][k]=\sum\limits_{i+j=k}f[x][i]\times f[y][j]\times C_{k-1}^{i-1}\times C_{siz[u]+siz[v]-k}^{siz[u]-i}\),这个转移是 \(O(n^3)\) 的,但是我们发现这个组合数与 \(f[y][j]\) 无关,也就是说可以用前缀和优化转移,这样复杂度就是 \(O(n^2)\) 的了。注意转移时根据遍历方向 \(j\) 的范围会不一样,需简单分个类。

trick:前缀和优化转移。

P7532 [USACO21OPEN] Balanced Subsets P

题意:有 \(n\times n\) 的网格,每个位置是 0/1,求有多少个 1 连通块满足同一行或列的两点间没有空。

思路:一开始想错了,想到今年 IOI D1T3 去了,后来才发现就是其实是凸多边形。

那么就比较简单了,设 \(f[i][l][r][0/1][0/1]\) 表示考虑到第 \(i\) 行,当前左右端点的状态,上一行是否包含这一行选的区间,然后用前缀和优化即可。

trick:对于转移是从矩形内转移过来,可以用二维前缀和优化。

[AGC007E] Shik and Travel

题意:一颗 \(n\) 个节点的二叉树,每个节点要么有两个儿子要么没有儿子。边有边权。

你从 \(1\) 号节点出发,走到一个叶子节点。然后每一天,你可以从当前点走到另一个叶子。最后回到 \(1\) 号节点,要求到过所有叶子并且每条边经过恰好两次

每天的路费是你走过的路径上的边权和,你的公司会为你报销大部分路费,除了你旅行中所用路费最高的,行走路线是从叶子到叶子的那一天的路费。

求路费的最小值。

思路:首先,显然可以二分答案。接着就是判定是否合法。设 \(dp[x][a][b]\) 表示在 \(x\) 的子树中,第一步走的距离是 \(a\),最后一步是 \(b\) 是否可行,转移时枚举 \(i,j\) 表示左右儿子之间的连边在子树中的部分,那么有 \(dp[x][a][b]=f[ls][a-w[ls]][i]\&f[rs][j][b-w[rs]](i+j+w[ls]+w[rs]\leqslant mid)\)

这样 DP 很暴力考虑怎么优化。我们发现,如果有两个合法状态 \(f[x][a][b],f[x][c][d](a\leqslant c,b\leqslant d)\),那么 \(f[x][c][d]\) 显然是没用的,于是可以把这样的无用状态去掉,即按 \(a\) 排序后,\(b\) 必须是递减的。在合并的时候,我们可以用双指针求出 \(ls,rs\) 中的每一对对应的状态,因此每次会增加 \(2\min(L,R)\),其中 \(L,R\) 表示左右子树中的状态数,因此最后的总状态数是 \(O(n\log n)\) 的。

trick:如果状态之间存在偏序关系,就可以优化状态量。

P1912 [NOI2009] 诗人小G

题意:把序列划分成若干段,每一段的代价是 \((\sum a_i-r-l-x)^p\),求最小代价。

思路:\(O(n^2)\) 的 DP 是好想的,考虑优化。

这个 \(p\) 次方很容易让人想到决策单调性,而且确实可以证明,可以根据导数是递增的来证。

于是可以用二分栈的经典做法来实现,复杂度 \(O(n\log n)\)

trick:决策单调性。

P3571 [POI2014]SUP-Supercomputer

题意:有一棵 \(n\) 个点的树,\(q\) 次询问,每次给出一个 \(k\),要求用最少的次数遍历完整棵树,每次可以访问不超过 \(k\) 个未被访问过且父节点已经被访问过的点。

思路:首先有结论:记 \(f_k\)\(k\) 的答案,那么有 \(f_k=\max(i+\left\lceil\dfrac{s_{i+1}}{k}\right\rceil)\),其中 \(s_i\) 表示深度不小于 \(i\) 的点的个数。这个式子的意思是先花 \(i\) 步走完前 \(i\) 层,然后在每次遍历 \(i\) 个点的次数。

我们来证明一下为什么是取 \(max\)。首先,如果不能花 \(j\) 步走完前 \(j\) 层,那么有 \(s_{i+1}-s_{j+1}>(j-i)\times k\),那么 \(f_i>f_j\),需要取 \(max\),其次,假设前 \(i\) 次每次走一层后不能每次走 \(k\) 个点,前 \(j\) 次每次走一层后可以每次走 \(k\) 个点,那么就有 \(s_{i+1}-s_{j+1}<k(j-i)\),于是 \(f_i>f_j\)。于是就证明了结论。

发现 \(f_k=\max(i+\left\lceil\dfrac{s_{i+1}}{k}\right\rceil)\) 可以用斜率优化,于是就可以做到 \(O(n)\)

P6047 丝之割

题意:有若干条形如 \((u,v)\) 的弦,可以进行若干次操作,每次操作选定 \((i,j)\),会切断所有满足 \(u>i,j<v\) 的弦会被破坏,代价为 \(a_i\times b_j\),求破坏所有弦的最小代价。

思路:先考虑那些弦不会做贡献,很容易发现如果有两个弦 \(i,j\) 满足 \(u_j>u_i,v_j<v_i\) 那么如果要删弦\(i\)就一定会同时删弦 \(j\),那么弦 \(j\) 就不做贡献。当我们把所有不做贡献的弦 \(j\) 删去后,就不会存在一条弦的 \(u\) 大于另一条弦的 \(u\)\(v\) 较小,换而言之就是弦的 \(u,v\) 都是单增的有了这个性质就很好DP了。

\(f_i\) 为破环前 \(i\) 个弦的最小代价,转移方程为 \(f_i=\min\limits_{j=1}^{i-1}(f_j+\min\limits_{k=1}^{u_j-1}a_k\times\min\limits_{k=v_i+1}^{n}b_k)\)

朴素的转移是 \(O(n^2)\) 的,我们考虑优化转移。我们发现,这个转移的形式似乎可以斜率优化,把 \(f_j\) 当成 \(y\),把 \(\min\limits_{k=1}^{u_j-1}a_k\) 当成\(-x\),把 \(\min\limits_{k=v_i+1}^{n}b_k\) 当成 \(k\),就和斜优的形式一模一样了,就可以愉快的套板子了。复杂度 \(O(n)\)

trick:斜率优化。

CF1188C Array Beauty

题意:定义一个序列的权值为两个数之差的绝对值的最小值,求长为\(n\)的序列\(a\)的所有长为\(k\)的子序列的权值之和

思路:首先肯定是对 \(a\) 数组排序。我们发现,这一题的值域很小,那做法可能和值域有关。答案的最大值肯定不会超过 \(\dfrac{a_n}{k-1}\),因此我们考虑枚举答案 \(v\),然后 DP 计算权值 \(\geqslant v\) 的方案数,然后直接将方案数相加即可。然后设 \(f[i][j]\) 表示当前在第 \(i\) 个数且选第 \(i\) 个数,已经选了 \(j\) 个且满足条件的方案数,所以 \(f[i][j]=\sum\limits_{a_i-a_k\geqslant v}f[k][j-1]\),然后用前缀和优化就是 \(O(nk)\) 的了。总复杂度 \(O(\dfrac{V}{k}nk)=O(nV)\)

trick:分析数据范围,斜率优化。

CF1179D Fedor Runs for President

题意:在树上删除一条边后,求最多能形成多少条简单路径。

思路:树上斜率优化。

首先只考虑加一条边后会多出来多少条路径。假设加入的边为 \((x,y)\),设 \(s_k\)\(path(x,y)\) 上一点 \(k\) 不向路径延伸的子树大小,那

\[ans=\dfrac{\sum\limits_{k\in path(x,y)}s_k(n-s_k)}{2} \]

\[=\dfrac{n\times\sum\limits_{k\in path(x,y)}s_k-\sum\limits_{k\in path(x,y)}s_k^2}{2} \]

\[=\dfrac{n^2-\sum\limits_{k\in path(x,y)}s_k^2}{2} \]

要让答案最大,我们就要选一条路径使 \(\sum\limits_{k\in path(x,y)}s_k^2\) 尽量小。

我们设 \(x\) 为当前点,然后计算每一条经过 \(x\) 的路径的答案。设 \(f_x\)\(x\) 的子树中让上式最小的路径,则 \(f_x=\min\limits_{y\in son(x)}f_y+(size_x-size_y)^2\) 其中 \(size_x-size_y\) 即为 \(s_x\)

求出这个后,考虑如何将两条路径合并。先朴素直接枚举两个儿子来转移,即

\[ans=\min\limits_{u,v\in son(x),u\neq v}f_u+f_y+(n-size_u-size_v)^2 \]

这样一次转移是 \(O(son_x^2)\) 的,但看到这个二次式我们尝试用斜率优化。我们枚举一个 \(u\),然后求出最优决策点 \(v\)。枚举两个点 \(i,j\) 假设 \(i\)\(j\) 更优,那么有

\[f_u+f_i+(n-size_u-size_i)^2\leqslant f_u+f_j+(n-size_u-size_i)^2 \]

化简后可得

\[f_i+size_i^2-2(n-size_u)size_i\leqslant f_j+size_j^2-2(n-size_u)size_j \]

再变形可得

\[(f_i+size_i^2)-(f_j+size_j^2)\leqslant2(n-size_u)(size_i-size_j) \]

因此当 \(size_i>size_j\) 时,

\[\dfrac{(f_i+size_i^2)-(f_j+size_j^2)}{(size_i-size_j)}\leqslant2(n-size_u^2) \]

然后再注意 \(size_i<size_j\) 时不等号变号,以及特判一下等于即可。还有就是我们按 \(size\) 大小先给所有儿子排序一遍后转移就很方便了。

总复杂度\(O(n\log(n))\)

trick:树上斜率优化。

P4292 [WC2010] 重建计划

题意:求有上下界树上平均值最大路径。

思路:有点分治的做法,感觉长剖比较好写,就来写长剖了。

对于每个点,设 \(f[x][i]\) 表示点 \(x\) 向下长度为 \(i\) 的链的最大权值,用长链剖分 + 线段树优化就可以做到 \(O(n\log n)\)

CF671D Roads in Yusland

题意:给定一棵 \(n\) 个点的以 \(1\) 为根的树。有 \(m\) 条路径 \((x,y)\),保证 \(y\)\(x\)\(x\) 的祖先,每条路径有一个权值。你要在这些路径中选择若干条路径,使它们能覆盖每条边,同时权值和最小。

思路:左偏树优化 DP。

\(f[x]\) 表示覆盖了 \(i\) 子树及 \(i\) 连向 \(fa[i]\) 的边的最小代价,我们发现转移时无法只保留最优的,因为我们的选择是有后效性的,但是可以选择的链总共只有 \(O(n)\) 个,于是可以考虑保留所有可能的最优方案,用左偏树来维护,转移时有全局加,合并,删除,都可以维护。复杂度 \(O(n\log n)\)

trick:如果发现状态中维护的不能只有答案,可以先保留所有对后面有贡献的方案,然后再想办法优化。

CF809D Hitchhiking in the Baltic States

题意:给出 \(n\) 个区间 \([l_i,r_i]\)\(n\) 个未知数 \(a_1,a_2,\dots,a_n\),现在你要确定这 \(n\) 个数,使得 \(a_i\in[l_i,r_i]\),并且这个序列的最长严格上升子序列尽可能大,求这个最大值。

思路:设 \(dp[i]\) 表示要让最长上升子序列长度为 \(i\) 时子序列最后一个的最小值。

考虑转移:

  1. \(dp[i-1]<l\),那么 \(dp[i]\leftarrow l\)
  2. \(dp[i-1]\in[l,r-1]\),那么 \(dp[i]\leftarrow dp[i-1]+1\)
  3. \(dp[i-1]\le r\),那么不变。

暴力转移是 \(O(n^2)\) 的,考虑用平衡树优化。

  1. 用小于 \(l\) 的最大的 \(dp[i]\) 转移到 \(dp[i]+1\)
  2. 让区间 \([l,r-1]\) 内的 +1,然后往后移。
  3. 删掉大于等于 \(r\) 的最小的数。

trick:如果转移形式简单,可以把相同转移的一起处理。

CF856F To Play or not to Play

思路:考虑一个最暴力的做法,设 \(dp[i][a][b]\) 表示前 \(i\) 个时段,V 的经验为 \(a\),P 的经验为 \(b\) 是否可行。

考虑怎么简化状态。我们发现,如果状态 \((a,b)\) 合法,那么 \((a-1,b),(a,b-1)\) 合法,于是我们只需要记录所有不存在 \((a',b')\) 满足 \(a'\ge a,b'\ge b\)\((a,b)\) 即可。

考虑现在加入了一个时段,长度是 \(len\)

如果只有 V 在线,那么所有的 \((a,b)\) 都会变成 \((a+len,b)\)

如果只有 P 在线,那么所有的 \((a,b)\) 都会变成 \((a,b+len)\)

如果两人都在线,那么分 3 种:

  1. \(|a-b|\le C\),那么会变成 \((a+2len,b+2len)\)
  2. \(a-b>C\),那么可以让两个人都上线变成 \((a+len,b+len)\),或者让 \(a\) 减小到 \(b+C\),那么会变成 \((b+C+2len,b+2len)\)
  3. \(b-a>C\),同上。

考虑用平衡树维护。

对于前两种,就是全局的 \(a\) 增加或者 \(b\) 增加。

对于后一种,我们把平衡树分成 \(a-b>C,|a-b|\le C,b-a>C\) 三部分,对于第一个部分,我们把 \(b\) 最小的一组变成 \((b+C,b)\) 然后加入第二部分,第三部分同理,然后合并时去掉会被二维偏序掉的状态即可。

trick:如果状态之间存在偏序关系,可以把被偏序掉的状态去掉,减少状态量;如果转移形式简单,可以把相同转移的一起处理。

CF1630D Flipping Range

题意:给定一个长度为 \(n\) 的数组 \(a\) 和一个包含 \(m\) 个正整数的集合 \(b\),保证对于所有的 \(1\leqslant i\leqslant m\),都有 \(1\leqslant b_i\leqslant \lfloor\frac n2\rfloor\)。你可以对 \(a\) 数组执行若干次操作,每次操作中,你可以:

  • 选出一个在 \(b\) 集合中出现过的数 \(x\)
  • 选择 \(a\) 数组中一个长度为 \(x\) 的子段,然后将该子段里的所有元素的值变为其相反数。形式化地来说,选出满足 \(1\leqslant l\leqslant r\leqslant n\)\(r-l+1=x\) 的两个数 \(l,r\),然后对于所有的 \(l\leqslant i\leqslant r\),将 \(a_i\) 替换为 \(-a_i\)

你希望最大化 \(\sum\limits_{i=1}^n a_i\) 的值,求这个最大值。

思路:好厉害的题目。

首先,如果 \(2^k\le n\),那么答案就可以取到下界 \(n\),于是现在的 \(k\) 降到了 \(O(\log n)\) 的量级。设 \(f[n][k]\) 表示答案,那么转移就是 \(dp[n][k]=\min\limits_{i=0}^{n-1}dp[i][k-1]+c(i+1,n)\)

考虑怎么求 \(c(l,r)\)

\[\begin{aligned} c(l,r)&=\sum\limits_{i=l}^r\sum\limits_{j=i}^{r}[\gcd(i,j)\ge l]\\ &=\sum\limits_{i=l}^r\sum\limits_{j=i}^{r}\sum\limits_{k=l}^r[\gcd(i,j)=k]\\ &=\sum\limits_{k=l}^r\sum\limits_{i=1}^{\left\lfloor\frac{r}{k}\right\rfloor}\sum\limits_{j=i}^{\left\lfloor\frac{r}{k}\right\rfloor}[\gcd(i,j)=1]\\ &=\sum\limits_{k=l}^r\sum\limits_{i=1}^{\left\lfloor\frac{r}{k}\right\rfloor}\varphi(i) \end{aligned}\]

\(S(i)=\sum\limits_{j=1}^i\varphi(j)\),于是可以整除分块在 \(O(\sqrt{r-l})\) 的时间内计算 \(c(l,r)\)

然后有 \(c(l,r)\) 满足四边形不等式。

证明:记 \(f(l,r,k)=\sum\limits_{i=l}^rS(\left\lfloor\frac{k}{i}\right\rfloor)\),那么对于 \(i\le j\le k\le l\),有:

\[\begin{aligned} c(i,l)+c(j,k)&=f(i,l,l)+f(j,k,k)\\ &=f(i,j-1,l)+f(j,l,l)+f(i,k,k)-f(i,j-1,k)\\ &=c(i,k)+c(j,l)+f(i,j-1,l)-f(i,j-1,k) \end{aligned}\]

因为有 \(k\le l\),因此 \(f(i,j-1,l)\ge f(i,j-1,k)\),于是有 \(c(i,k)+c(j,l)\le c(i,l)+c(j,k)\)

然后就可以用分治解决,复杂度 \(O(n\log^2n\sqrt{n})\)

不过可以做到 \(O(n\log^3n)\)。类似 P5574 [CmdOI2019] 任务分配问题,可以考虑维护端点 \(l,r\) 然后在每次移动时计算贡献。左端点的移动是 \(O(1)\) 的,右端点移动时,要对满足 \(\left\lfloor\frac{r-1}{k}\right\rfloor<\left\lfloor\frac{r}{k}\right\rfloor\)\(k\) 将贡献加上 \(\varphi(\left\lfloor\frac{r}{k}\right\rfloor)\),而这样的 \(k\) 一定是 \(r\) 的因数,于是复杂度就是 \(O(n\log^3n)\) 的了。

CF1119F Niyaz and Small Degrees

题意:一棵大小为 \(n\) 的树,有边权,设 \(f(x)\) 表示要满足所有点的 \(deg\leqslant x\) 所要删掉的边的边权和的最小值,求出 \(f(0)\)\(f(n)\)

思路:先考虑对于每个 \(x\) 计算答案。设 \(dp[i][0/1]\) 表示 \(i\) 向上连的边删或不删时的最小代价。转移时,对于 \(i\) 的每个儿子 \(j\),有两种贡献,\(a_1=dp[j][0]+w\) 表示删掉 \((i,j)\) 的贡献,\(a_2=dp[j][1]\) 表示不删 \((i,j)\) 的贡献。我们考虑先取所有的 \(a_2\),用堆来维护所有的 \(a_1-a_2\),然后选最小的一些\(a_1-a_2\)\(a_2\) 替换掉,这一步可以用堆维护。单次复杂度 \(O(n\log n)\)

然后考虑正解。如果从小到大考虑每个 \(x\),那么一个 \(deg=x\) 的点对 \([x,n-1]\) 是没有贡献的,对于每个点只需考虑 \(x<deg\) 的情况,这样的量级是 \(\sum deg=n\) 的。我们将 \(deg\leqslant x\) 的点视为无用点,其他为有用点,然后从每个有用点开始 dfs,把无用点视为叶子,这时每个无用点的 \(a_1=w,a_2=0\),它对它相邻的有用点的贡献即为 \(a_1-a_2=w\),然后像暴力做法一样加入有用点的贡献,最后再加上一直弹堆顶到堆中元素为须删掉的边数时,堆中所有 \(a_1-a_2\) 的和就是答案。注意更新答案后要撤销加入的有用点的贡献,并撤销不断弹出堆顶直到堆中元素符合要求时删除的无用点的贡献,需要用可删除堆维护。总(均摊)复杂度 \(O(n\log n)\)

P1295 [TJOI2011] 书架

题意:给一个序列 \(h\),要分成若干段使得每一段长度不大于 \(m\),最小化所有段的最大值之和。(其实这题本来不是这个题单里的)

思路:首先可以很简单的想到一个 \(O(n^2)\) 的 DP:设 \(f_i\) 表示 \(i\) 为当前段末尾的最小和,则 \(f_i=\min\limits_{s_i-s_j<=m}(f_j+\max\limits_{k=j+1}^ih_k)\)。因为有最大值,我们考虑找找单调性。首先,如果 \(h_j\leqslant h_{j+1}\),那么 \(\max\limits_{k=j}^i h_k=\max\limits_{k=j+1}^i h_k\),而 \(f\) 显然是单调递增,那这个时候从 \(j\) 转移一定比从 \(j+1\) 转移更优,所以我们很容易用一个单调队列(记为 \(q\))来记录可能对答案做贡献的位置。然后我们考虑如何计算贡献。因为队列里的值是递减的,即 \(h_{q_l}>\cdots h_{q_r}\),那么每个的贡献就是 \(f_{q_l}+a_{q_{l+1}}\),这时我们还需维护 \(f\)。为了保证复杂度,我们可以用两个单调下降的栈来维护 \(f\),每当左或右端点到了中点就重构,这样复杂度就是 \(O(n)\) 的了。

[ARC082F] Sandglass

题意:有一个沙漏,每秒会有 1g 沙子从上面落下来,有 \(k\) 个时刻 \(r_1,r_2\cdots r_k\),每到 \(r_i\) 沙漏就会翻转,多次询问如果一开始上部有 \(a_ig\) 沙子,在时刻 \(t_i\) 时上部有多少沙子。

思路:一开始的思路是每个操作形如 \(x\rightarrow max(x-\Delta r,0)\),可以按时间轴用线段树维护答案,没想到这题竟然可以做到线性。

其实线性做法的思路也是维护如果一开始是多少此时就有多少,记为 \(f_i\),那么可以把 \(f_i\) 的图像画出来,发现就像这样img最终值不和上下界(红线)相等的 \(f_i\) 必然满足在一个区间内,因此我们只用这个区间的左右端点维护上下界即可做到线性。

trick:通过画图分析转移的性质。

P2519 [HAOI2011] problem a

题意:一次考试共有 \(n\) 个人参加,可能出现多个人成绩相同的情况。第 \(i\) 个人说:“有 \(a_i\) 个人成绩比我高,\(b_i\) 个人成绩比我低。”

请求出最少有几个人没有说真话。

思路:发现条件等价于 我是第 \(a_i+1\) 名,有 \(n-a_i-b_i\) 个人和我的分数相同,相当于是将分数从小到大排序后,与第 \(i\) 个人分数相同的区间是 \([a_i+1,n-b_i]\)

如果超过 \(r-l+1\) 个人的区间都是 \([l,r]\),那么最多只有 \(r-l+1\) 个人满足条件。

如果把有多少人的区间是 \([l,r]\) 当成区间 \([l,r]\) 的贡献,那么我们要求的就是选择若干不交区间,求最大代价。

\(f[i]\) 是前 \(i\) 个区间的最优解,转移时枚举最大的 \(j\) 满足 \(r_j<l_i\),有 \(f[i]\leftarrow f[j]+val[i]\)

复杂度 \(O(n\log n)\)

P9523 [JOISC2022] 复制粘贴 3

题意:构造长度为 \(n\) 的目标串,初始 \(X\) 为空,支持三种操作:

  • 输入 c,即 \(X\gets X+c\)
  • 剪切,将 \(X\) 剪切到剪切板 \(Y\),并令 \(X\) 为空。
  • 粘贴,即 \(X\gets X+Y\)

分别有代价 A,B,C,求得到目标串的最小代价。

思路:设 \(f[l][r]\) 表示当前区间的答案,那么转移有两种,一种是 \(f[l][r]=A+f[l][r-1]\),一种是选择 \(k\),然后将 \(s_{[l,r]}\) 中删掉尽量多的 \(s_{[l,k]}\)

直接转移复杂度很劣,考虑怎么优化。我们一加上 \(f[l][r]=A+f[l+1][r]\),这样我们进行剪切操作的转移就可以钦定在 \(s_{[l,p]}=s_{[q,r]}\) 时才进行。

预处理出 kmp 数组,然后每次跳 next 来转移,这样做是 \(O(n^2)\) 的。

现在还需快速求出 \([l,r]\) 中可以选出多少个 \([l,p]\)。可以预处理出倍增数组 \(g[l][r][k]\) 表示和 \(s_{[l,r]}\) 相同的不重复的后面 \(2^k\) 个子串的右端点,然后就可以快速求了。

复杂度 \(O(n^2\log n)\)

LOJ#6564. 最长公共子序列

题意:求 LCS。

思路:一直没怎么做过bitset科技题。

朴素求LCS的DP是 \(O(nm)\) 的,但是我们发现 DP 的差分数组 \(c[i][j]=dp[i][j]-dp[i][j-1]\) 只有 0/1 两种取值,这启发我们用 bitset 来优化。考虑加入 \(a_{i+1}\) 时,对于原来差分数组的每一段极长连续段满足 \(c[i][l],c[i][l+1]\cdots c[i][r-1]=0,c[i][r]=1\),我们找到最左的满足 \(b_j=a_{i+1}\),于是有 \(v[i+1][j]=1,v[i+1][r]=0\),如果找不到就不变。

考虑怎么用 bitset 来进行操作。

\(f\) 表示当前的差分数组,\(g\) 表示 \(f<<1\)\(f'\) 表示在 \(f\) 所有匹配位置变成了 1 后的数组,那么令 \(g'=f'-g\),再 \(f'\leftarrow f'\&(f'\oplus g')\) 即可。

trick:如果 DP 数组的差分只有 0/1 可以考虑用 bitset 来优化转移。

posted @ 2024-02-15 20:37  Xttttr  阅读(22)  评论(0编辑  收藏  举报