从Shopping Plans谈起的一类的经典 kth 问题

一类 kth 相关问题

​ 实际上,这个 trick 以及其拓展在去年 noip 级别的模拟赛已经用到了多次,但是认知仅局限于“用优先队列”解决的一类题目,有时可以直接建立最短路模型。在去年一场 nfls 中甚至因为知道此 trick 而在最后一题狂砍 50 分,今天在 dmy 再遇,故整理其成体系。

T4 控制

n 类装备,每种类型有 mi 种,且各有一个魔力值,定义一个方案为每一类选一个;你要求出前 k 大方案的魔力值总和。

套路题,优秀的暴力是 用优先队列维护,类似搜索,从一个状态向外转移,并加入堆里。为了避免加重,需要加入顺序的约定。

正解比较复杂,但发现本质和 dij 很像,所以可以转换成图论,建立 nm 个点,每个第 i 类的点,向每个第 i+1 类的点连边。直接可并堆优化 k 短路即可。需要虚拟源点和汇点。

0.模型与思路

给定你一个价值的计算方案,具体又可分为序列上子集和,子区间(超级钢琴),树上连通块。要求输出其第 k 大的价值,或前 k 大价值的和。k 通常在 105 级别,而还有一类 k=1018 类似的问题通常是使用二分答案解决。

这一类问题有通用思路:

对于目前考虑到的一个状态 S,设 trans(S)S 的后继状态集合。首先将最优的 S 放进堆中,重复下列操作 k 次。

  • 取出堆顶 S,计算 S 的答案。
  • 将所有 trans(S) 放入堆里。

可以注意到,如果 |trans(S)|O(1) 或者 O(log) 的,且都劣于 S,而且需要保证 S 的状态不重不漏时算法可以在 O(nlogn) 或者 O(nlog2n) 的时间复杂度内完成。问题的关键在于构造一个不重不漏trans(S)

1.Multiset

给定一个可重集 S,求大小为 p 的第 k 小子集。

首先排序,最优一定是前 p 项。先不考虑 |trans(S)| 大小的限制,先满足不重不漏,因为这样我们至少能得到多项式复杂度的做法。

初始时 S0 为排序后的前 p 个数,考虑以下构造方案:

  • 每次将最右侧未移动点向右移动若干位,但不能跨越已移动点
  • 将其标记为已移动点

容易发现,对于任何一个大小为 p 的状态,都有且仅有一种操作方案。实际上类似于二进制上逐位确定的方法。

我们可以将转移抽象为树的形式,这样做的问题就在于,节点的儿子实在太多了。

但是只要保证解是逐渐变劣的,那么这棵树的形态是不重要的,那么我们可以用树的兄弟儿子表示法将树三度化,也就是说,每个节点只存它最优的儿子和向右第一个兄弟。

设四元组 (val,x,y,z) 表示权值和为 val,最长未移动前缀为 x,当前移动项为 y,右侧首个已移动项为 z

  • (val,x,y,z)(valvy+vy+1,x,y+1,z) 表示最右侧的可移动点向右移动一格,也就是向右第一个兄弟。需要保证 y+1<z
  • (val,x,y,z)(valvx+vx+1,x1,x+1,y) 表示将当前 x 向右移动一格。也就是最优的儿子。

不难发现,x,y 的意义相当于于下次操作和这次操作的位置。初始加入 (sum,p,n+1,n+1)。用小根堆维护,时间复杂度 O(nlogn)。如果拓展至大小在 [l,r] 区间中的子集,那么初始是对每个 p[l,r] 都加入即可。注意这里 n+1 实际上是 inf

例题:CF1250I。

2.Arrays

n 个数组,每个数组选一个数,求第 k 小和。

O(n2log) 的做法即是记录每个数组选到第几个位置。(注意去重:格外记录 i 表示钦定前 i 个已经选定了)。

最小的肯定是每个数组选第一个数。套路的,每个数组内部升序排序,设 (val,x,y) 表示当前考虑到第 x 个数组,第 x+1n 的数组全部只选第一个元素,第 x 个数组选第 y 个元素,这样同样可以保证转移只会变劣不会变优:

  • (val,x,y)(val,x+1,1) 表示考虑到下一个数组。
  • (val,x,y)(valax,y+ax,y+1,x,y+1) 表示考虑当前数组的下一个位置。
  • 初始为 (ai,1,1,1)

但是这样会算重,很简单,你在 {2,1,1,1,1} 往下还会转移到 {2,1,1,1,1}。所以我们钦定每次只能向后转移到 (x+1,2)

那当前数组想取第一个位置怎么办呢?我们只需在每个 (x,2) 新增第三个转移:

  • (val,x,2)(valax,2+ax,3,x,3)
  • (val,x,2)(valax+1,1+ax+1,2,x+1,2)
  • (val,x,2)(val(ax,2ax,1)+(ax+1,2ax+1,1),x+1,2)

但是注意:我们仍然要保证不断变劣(递增),而 (ax,2ax,1)+(ax+1,2ax+1,1) 可能是负数,但是我们只需要数组间按照 (ax,2ax,1) 递增排序即可。有点类似“反悔”的操作。

细节:对于长度为 1 的数组,我们必须选且不能改变,所以提前算出这些的和,不把他们插进 vector 里即可。

例题:P2541 [USACO16DEC] Robotic Cow Herd P。

与 k 短路的联系:不难发现本题可以直接图论建模,分层图求 k 短路,不过有些小题大做了。

3.Multisets(Shopping Plans)

n 个物品,每个物品有种类 a 和价格 c

对于第 j 个种类,购买个数必须在 [xj,yj] 的限制内。输出价格前 k 小的方案。

将两者结合起来。外层仍然是 Arrays 的做法,转移到当前数组的下一个位置 (x,y)(x,y+1) 就是 Multiset 取到下一个子集。

数组间是按照 aj,xj+1aj,xj 排序的。

说起来容易,实现起来或许非常困难。主要在于本题出人意料的特殊情况处理。

4.Tree

求树上第 k 小连通块点权和。

先考虑包含根的连通块,对有根树求出 dfs 序,建立一张新图:

  • ii+1 表示选择 idi 这个点。
  • ii+sizi 表示跳过 i 的子树。

然后问题转换为 DAG k 短路。外面再套一层点分治,即可将无根转换为有根。复杂度 O(nlog2)

5.Permutation

雷暴太强大:关于一类求前 k 优解的问题 - Cry_For_theMoon - 博客园 (cnblogs.com)

posted @   Apricity8211  阅读(20)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示