基站选址
首先我们需要一个暴力 dp,首先一个思路是设 $f_{i,j}$ 表示设置 $i$ 个基站,前 $j$ 个的代价,但是这样不好转移,主要问题是不知道 $j$ 是否会被后面的基站所覆盖 ,一个常见思路是我们可以强制第 $j$ 个放一个基站,这样就变得好转移了,因为我们相当于认为这个点一定被覆盖了,代价也就变得确定了。
当然为了计算出最后的答案,我们可以在最后加上一个村庄。
有了状态, dp 方程变得显然了。我们有:
$$
f_{i,j}=\min\limits_{0\le k<j}{ f_{i-1,k} +\sum\limits_{k\le l\le j,d_{l}+s_l<d_j,d_l-s_l>d_k}w_l }+c_j
$$
其中当 $i=1$ 的时候,注意到 $f_{i-1,k}$ 其实是没有什么意义的,设置为 $0$,而后面 $\sum$ 中的第三个限制也不复存在了。
我们用线段树来做,那么我们线段树中的就需要维护每个点的代价,如果一个区间,基站建在右端点右边,那么左端点再往左的区间,如果上一个基站建在这个区间中,那么就会有 $w$ 的代价,相当于区间加代价。
那么对于 $i=1$ 来说,其实是比较难处理的一个点,现在看来,把所有的 $f_{0,i}$ 设置为 $0$,然后每次全局覆盖是最优的。
代码:
1 |
|
APIO2016烟火表演
首先考虑一个暴力 DP,设 $f_{i,j}$ 表示以 $i$ 为根的子树所有烟火都在 $j$ 时爆炸的代价,显然状态数就炸了,不过显而易见的是 $f_{i,j}$ 是一个下凸的函数,而且是一个一次分段函数。
接下来是这个题的精髓部分,我们怎么优化呢。首先我们需要一个数据结构来维护这个函数,因为有合并操作,所以我们采用左偏树来做合并,我们让这个下凸包的斜率是依次减 $1$ 的,要做到这一点只需要多次加入拐点,也就是说,如果一个拐点两边斜率相差 $2$,那么我们就插入两个拐点进去,对于每个点,我们只记录其横坐标,而不关心他们在 $y$ 方向的平移。
那么现在我们假设拿到了 $u$ 的函数,$u$ 的父亲是 $v$,之间连得边长度为 $l$,设 $L,R$ 为 $u$ 的函数中斜率为 $0$ 的函数的两个端点,那么我们显然有:
$$
\begin{cases}
f_v(x)=f_u(x)+l &(x\le L)\
f_v(x)=f_u(L)+l-(x-L)&(L<x\le L+l)\
f_v(x)=f_u(L)&(L+l<x\le R+l)\
f_v(x)=f_u(L)+(x-R)-l&(x>R+l)
\end{cases}
$$
所以我们要做的是把最右边一段斜率改为 $1$,做到这个只需要删除最大值直至只剩下一个最大值,这是因为斜率为 $0$ 的那一段右边只可能有一段。
接下来我们把斜率为 $0$ 的那段向右平移,只需要把最大值和次大值拿出来即可。
最后我们需要插入一段斜率为 $-1$ 的段,其实这个第二步我们已经实现了。
至于叶子,我们需要特殊判断。左偏树维护函数很好的题目。
代码:
1 |
|
ARC070E NarrowRectangles
这个题实际上是烟火表演的弱化版,不过还是略有不同:
- 首先,一样用堆来维护函数,不过因为不需要支持合并,所以普通的堆即可。
- 因为最右边或最左边的斜率难以确定,我们这里采用用两个堆来维护一个函数的方法,即设 $L,R$ 为斜率为 $0$ 的一段的左右端点,我们 $L$ 左边维护一个大根堆,$R$ 右边维护一个小根堆,我们并不关心除堆顶之外的元素。利用上个题的技巧:加点加多次来让相邻点的斜率之差为 $1$,我们可以维护这个函数与绝对值函数的加法。
- 以及,我们需要维护斜率为 $0$ 段的左右端点的平移,这个直接打标机即可,最左和最右两边可能会有一些段不合法,不过我们不去管他。
- 在上个题中,我们是采用倒推的方法,所以左右边界要卡的很准。不过这个题,因为符合的函数只有绝对值函数,所以我们可以直接维护答案是多少。
代码:
1 |
|