Loading

dp 练习题 2024.3.14

ARC070E NarrowRectangles

link

题意:平面上有 \(n\) 个矩形,左下角点坐标为 \((l_i,i-1)\),右上角点坐标为 \((r_i,i)\)。每次把一个矩形沿着横轴方向移动一个长度单位,求移动多少次使得任意两个相邻矩形存在交点。

\(1\le n\le 10^5,\space 1\le l_i< r_i\le 10^9\)

考虑最简单的 dp,设 \(f[i,j]\) 表示处理了前 \(i\) 个矩形,并且第 \(i\) 个矩形左边界移动到了 \(j\) 位置的最小代价。

有转移:

\[f[i,j]=|j-l_i|+\min_{j-len_{i-1}\le k\le j+len_i}\{f[i-1,k]\} \]

一个很巧妙的 trick:考虑 \(|j-l_i|\) 是凸函数,并且凸函数的区间平移最小值还是凸函数,可以归纳证明 \(f[i,]\) 也是凸函数。

\(f_i(x)\)\(f[i,]\) 在图像上的函数,设 \(L,R,v\)\(f_{i-1}(x)\) 上最小值的区间,以及对应的最小值,那么:

\[f_i(x)=|x-l_i|+\begin{cases} f_{i-1}(x+len_i) & x<L-len_i \\ v & L-len_i\le x\le R+len_{i-1} \\ f_{i-1}(x-len_{i-1}) & x>R+len_{i-1} \end{cases} \]

忽视前面的 \(|x-l_i|\),我们相当于是把原来的 \(f_{i-1}(x)\)\(L\gets L-len_i,\space R\gets R+len_{i-1}\)

然后我们需要加上 \(|x-l_i|\) 这个绝对值函数,本质是把 \([-\infty,l_i]\) 这一整段函数斜率整体 \(-1\)\([l_i,\infty]\) 这一整段函数整体 \(+1\)。具体的,分类讨论:

  • \(L\le l_i\le R\)

此时最小值只有一个位置 \(l_i\),把 \(l_i\) 左边斜率 \(-1\),右边斜率 \(+1\)

  • \(l_i<L\)

原来在 \(l_i\) 右边的斜率为 \(-1\) 的这一段斜率会变成 \(0\)。设斜率为 \(-1\) 的这一段函数为 \([s,t]\),令 \(s'\)\(\max(s,l_i)\),那么新的 \(L'=s',R'=t\),然后原来 \(l_i\) 右边的斜率全部 \(-1\)\([l_i,t]\) 斜率全部 \(+1\)\(R'\) 右边的斜率整体 \(+1\)

  • \(l_i>R\)

同理。

考虑使用两个堆 \(q_l,q_r\) 分别维护 \(L\) 左边、\(R\) 右边的斜率拐点(严格来说是斜率 \(+1\)\(-1\) 的分界点)。\(q_l\) 是大根堆,\(q_r\) 是小根堆,所以 \(L,R\) 其实是 \(q_l,q_r\) 各自的堆顶。

每次需要支持找到 \(q_l\)\(q_r\) 的前两个拐点,或者给 \(q_l\)\(q_r\) 中的拐点整体偏移,后者可以维护两个堆的整体偏移标记。

时间复杂度 \(O(n\log n)\)记录


Luogu6789 寒妖王

link

题意:一张 \(n\) 个点 \(m\) 条边的有边权的无向图,每条边有 \(50\%\) 的概率出现或消失,求可以选出的最大基环树森林的边权和期望值,答案对 \(998244353\) 取模。

\(1\le n\le 15,\space 1\le m\le 60\)

把期望变成总和,最后除以 \(2^m\)

考虑类似于 kruskal 的过程,边按边权从大到小排序,依次选择,如果可以加就加。

所以我们实际上要算的是每条边可以加入的方案数。

一开始的想法是设 \(f[i,S],g[i,S]\) 表示考虑了前 \(i\) 条边,\(S\) 点集组成极大树/基环树,点集 \(S\) 内和 \(S\) 外的点与 \(S\) 的连边的方案数。

如果第 \(i\) 条边不出现:\(f[i,S]=f[i-1,S],\space g[i,S]=g[i-1,S]\)

如果出现,讨论:

  • \(S\) 包含了 \(u_i,v_i\)

\(f[i,S]=\sum\limits_{T\subset S,u_i\in T,v_i\in S-T} f[i-1,T]f[i-1,S-T]\)

\(g[i,S]=g[i-1,S]+f[i-1,S]+\sum\limits_{T} (f[i-1,T]g[i-1,S-T]+g[i-1,T]f[i-1,S-T])\)

  • \(S\) 包含其中之一:\(f[i,S]=f[i-1,S],\space g[i,S]=g[i-1,S]\)

  • \(S\) 不包含:空

计算答案很好做。

但是这样是错误的,当这条边连接的是两个基环树,那么此时出现/不出现都不会被选择。但是我们算 \(g[i,S]\) 时会默认这条边不选,这样就少算了一些方案。

所以不能把“基环树”作为状态,重新定义 \(g[i,S]\) 表示连通图的方案数。

我们可以发现,此时 \(S\) 要么是一棵连通的树,要么是若干个基环树(之间的边可以出现,但是不会被选)。

考虑计算不选择\(i\) 条边的方案数,显然要么两个点所在同一个连通块且不是树,要么处于两个不是树的连通块,不是树的方案数是 \(g[i,S]-f[i,S]\)

时间 \(O(m\log m+3^nm)\)记录


Luogu7897 spxmcq

link

题意:一棵 \(n\) 个点的树,每个点权值为 \(a_i\)\(m\) 次询问,每次给出 \(u,x\),求如果\(u\) 子树内每个点权值加上 \(x\),然后在子树内选择一个包含 \(u\) 的连通块,连通块内点权和最大值。

\(1\le n,m\le 10^6,\space |a_i|,|x|\le 10^8\)

考虑有 \(x\) 这个未知数,使用普通的 dp,我们可以把 \(f[u]\) 表示成一个一次函数 \(k_ux+b_u\)

维护转移,一个儿子 \(v\) 的决策可能发生变化:从贡献 \(0\) 变成 \(f[v]\)\(k_vx+b_v\),变化条件是当前的 \(x\) 满足 \(k_vx+b_v>0\)

离线下来,我们从小到大枚举 \(x\),这样有个结论:对于每个点 \(v\),父亲 \(u\)\(v\) 的决策都恰好变化一次。

证明比较简单,考虑 \(k_vx+b_v\) 图象是单调不降的,每一段斜率会越来越大,所以与 \(y=0\) 的交点恰好一个。

\(v\) 决策变化时,我们对于 \(u\) 向上一条“决策修改”过的链上的点,对应的 \(k,b\) 都要加上 \(k_v,b_v\),这个可以通过 dfs序 + 树状数组 处理。

然后这条链链头的点决策还没有变化,我们会使他的“修改决策时刻”变化。使用堆代替 \(x\) 的枚举,时间复杂度 \(O(n\log n+m\log m)\)记录


AGC004E Salvage Robots

link

题意:一个 \(n\times m\) 的网格,上面有一些机器人和一个出口。每次把所有机器人往上/下/左/右方向移动一格,机器人遇到出口会逃脱,超出边界会消失,求最多让多少个机器人逃脱。

\(1\le n,m\le 100\)

套路,视为把出口移动。出口带有一个 \(n\times m\) 的矩形,超出矩形边界的机器人会消失。

考虑消失的机器人一定是最上面若干行、最下面若干行、最左边若干行、最右边若干行的分布,并且在这些行和列消失后,会存在一个极大的“自由”矩形。在这个矩形中,出口可以自由移动并且不会有行和列消失。

\(f[i,j,k,l]\) 表示下面消失了 \(i\) 行、上面消失了 \(j\) 行,左边消失了 \(k\) 行,右边消失了 \(l\) 行时,除了极大自由矩形,还能让最多多少个机器人逃脱。

每次出口在极大自由矩形上,向上/下/左/右扩展一行或一列,然后相反方向的边界就会消失一行或一列。

使用记忆化搜索,时间复杂度 \(O(n^2m^2)\)

状态数一定不多,用 vector 存储。记录


AGC009E Eternal Average

link

题意:有 \(n\)\(1\)\(m\)\(0\),每次将 \(k\) 个数字合并,合并后的数值是原来这些数的平均值,求若干次合并后剩下一个数的取值方案数,答案模 \(10^9+7\)

\(1\le n,m\le 2000,\space 2\le k\le 2000\),保证 \(n+m-1\)\(k-1\) 的倍数

我们把原问题双射:给每个数分配一个 \(\dfrac 1k\) 的正整数次幂的系数,所有数系数之和等于 \(1\),求 \(n\)\(1\) 分配的系数之和。

发现这个双射是成立的。我们考虑允许一些系数为 \(0\) 的情况,这样可以简化问题。

可以考虑 \(n\)\(1\) 分配了多少个 \(\dfrac 1k,\dfrac 1{k^2},\dfrac 1{k^3}\),每种系数个数 \(\le k-1\)

由于所有系数加起来是一,所以我们应该给 \(m\)\(0\) 分配凑出来是 \(1\) 的系数。具体的,如果 \(n\)\(1\) 分配了 \(t\)\(\dfrac 1k\),并且至少分配了一个 \(\dfrac 1{k^2}\),那么 \(m\)\(0\) 需要分配 \(k-1-t\)\(\dfrac 1k\)

话句话说:对于不是最后一种的系数,两边分配的个数 \(=k-1\);对于最后一种系数,两边分配的个数 \(=k\)。类似于 \(k\) 进制小数加法,这样系数总和刚好是 \(1\)

考虑 dp,设 \(f[i,j]\) 表示 \(i\)\(0\)\(j\)\(1\) 分配系数的方案数。

每次分配下一种系数,枚举数字 \(1\) 分配 \(x\) 个,数字 \(0\) 分配 \(k-1-x\) 个,贡献是 \(\sum\limits_{x=0}^{k-1} f[i-x,j-(k-1-x)]\)

如果当前是最后一种系数,贡献是 \([i+j\ge k](k-1)\)

但是现在每个数字的系数都不能是 \(0\),考虑把某个数的系数不断拆分,每次拆多出 \(k-1\) 个系数。

这样最后一种系数的贡献变为 \(\sum_{x=1}^{k-1} [(k-1)|(i-x)][(k-1)|(j-(k-x))]\)

注意 \(x\) 不能取 \(0\)\(k\),因为某一边的同种系数个数必须 \(\le k-1\)。前缀和优化,时间复杂度 \(O(n^2)\)记录


AGC007E Shik and Travel

link

题意:一棵二叉树,每个点要么没有儿子要么有两个儿子,每条边有费用。一个人一开始从任意一个叶子开始走,每天可以走到另一个叶子,当天费用是路径上的边费用总和。现在需要使得每条边都恰好经过两次,求这个人所花费用最大的那天的花费最小值。

\(n,w\le 131072\)

二分,转判定。

考虑点 \(u\),进入 \(u\) 后必须走完所有叶子,才能出去。

\(f[u,i]\) 表示从 \(u\) 进入,到第一个叶子的花费是 \(i\),从最后一个叶子走到 \(u\) 的最小花费。

注意到 \(f[u,]\) 的本质是若干对二元组 \((x,y)\),表示走到第一个叶子花费是 \(x\),从最后一个叶子走回来花费是 \(y\)

两个儿子合并,分为先走左边和先走右边两种情况。先走左边,对于左边的每一对二元组 \((x_l,y_l)\),和右边的一对二元组 \((x_r,y_r)\) 合并变为 \((x_l+w_u,y_r+w_u)\),其中 \(w_u\)\(u\) 与父亲的连边的费用,合并合法的条件是 \(y_1+x_2\le mid\)

这样做,一个子树的二元组个数上界是 \(O(siz)\),使用双指针合并,时间复杂度 \(O(n^2)\)

注意到如果有两对二元组 \((x_1,y_1),(x_2,y_2)\),必须满足 \(x_1<x_2,y_1>y_2\)\(x_1>x_2,y_1<y_2\),否则必定有一对是没有用的。

我们把没有用的去掉,会发现时间复杂度发生了变化。

设两个儿子二元组个数分别是 \(s_l,s_r\),不妨设 \(s_l\le s_r\),先走左边合并出来的二元组不超过 \(s_l\)

先走右边,因为合并出来的二元组中“最后一个叶子走回来的费用”是左边拿来的,即一共有 \(s_l\) 种。如果去掉没用的,其实合并出来的个数也会不超过 \(s_l\)

这样一来,合并出的二元组数量是 \(s_u=2\times \min(s_l,s_r)\)。本人不会算时间复杂度,于是写了个程序跑了下发现二元组个数和上界是 \(?\times 10^6\) 的,似乎是 \(O(n\log n)\) 的。这样就能过了,时间复杂度 \(O(n\log n\log V)\)

题解似乎使用 sort 代替了双指针,跑的时间竟然不到自己双指针的 \(2\) 倍?承认自己常熟实在太大了。记录

posted @ 2024-03-14 16:10  Lgx_Q  阅读(22)  评论(0编辑  收藏  举报