一些前 k 优化问题
前 \(k\) 优化问题
shopping plan.
主要探讨关于「求前 \(k\) 最优解」的问题。
原理类似于 \(Dijkstra\) 的堆优化,就我们一般可以轻松找到最小状态,然后从最小状态开始,重复地把常数个后继状态压入堆中,然后取出堆顶作为新的最小状态,直到 \(k\) 个状态被取出。
而最主要的问题是:
- 每个状态都能通过该拓展方式得到,即最后是能不漏的将所有状态拓展到的。
- 从当前状态拓展到后继状态时,权值(或者状态的答案)要单调(就像普通的 \(dij\) 不能跑有负权的图一样)。
- 每个状态可能被重复到达,我们需要一个方法去重,一般可以在拓展时加钦定使得每个状态的到达方法是唯一的,或者手动去重。
Shopping Plans on Multiset
-
给定一个可重集 \(S\),定义他的一个子集 \(T\) 是合法的,当且仅当 \(|T|\in [l,r]\),求出前 \(k\) 小的合法「子集和」。
\(|S|\leq 2\times 10^5,V\leq 10^9\)。
可以先考虑当 \(l=r\) 时怎么做,显然先将 \(S\) 从小到大排序,那么最左的 \(l\) 个数是最小的。
考虑构造一种拓展方式能不漏的覆盖所有状态,对于一个的合法的状态,我们可以将最左的可以向右移动的数向右移动若干位,但是不能跨越已选的数,这样显然覆盖了所有状态而且还满足每种状态的到达方法是唯一的,即满足不重不漏。
但是这样每次拓展是 \(O(n)\) 级别的,我们需要常数级的,那么我们可以钦定他每次只移一位。
那么怎么记录状态呢,朴素的想,可以用二进制,但是显然不对且无法优化,那么我们可以定义 \((sum,i,j,k)\),表示现在移动的值位置是 \(j\),他后面那个选择的数在 \(k\),前面的在 \(i\)(显然根据定义,\(1-i\) 连续),当前状态和为 \(sum\)。
那么转移有两种:
- 将当前移动的数再往后移一位,显然这需要 \(j+1<k\),即 \((sum,i,j,k)\to (sum+a[j+1]-a[j],i,j+1,k)\)。
- 将当前移动的数确定下来,移动的数变为前一个,即 \((sum,i,j,k)\to (sum,i-1,i,j)\),然后你就发现当前的状态会被重复计算,那我们只好强制给前面那个数移一位了,即 \((sum,i,j,k)\to (sum+s[i+1]-s[i],i-1,i+1,j)\),限制为 \(i+1<j\ \&\ i>0\)。
初始化很显然选最左的 \(l\) 个数是最小的,即 \((\sum_{i=1}^l a_i,l-1,l,n+1)\)。
但是啊,就有一个问题了,我们一种方案(决策)确实可以对应唯一的状态,但是我们的状态并不能对应唯一的方案啊。
比如 \((sum,5,8,10)\),实际上可能是选了 \((3,4,5,8,10,11)\) 也可能是 \((3,4,5,8,10,13)\)。
事实上,我们前面强调了每种决策由唯一的拓展路径到达,所以并不会导致重复,所以通过这个可以注意到,我们的状态并不像有一些的 dp 一样,一种决策对应唯一的状态,一种状态也对应唯一的决策,而是决策能对应唯一的状态,而状态并不能对应唯一的决策。
那么对于 \(l\neq r\) 的情况,我们直接令初始状态变为 \(r-l+1\) 个,将所有长度的最优都加进去即可。
Shopping Plans on Arrays
-
给定 \(n\) 个数组,要求每个数组中恰好选一个数,求前 \(k\) 小的方案。
\(n,k,\sum len\leq 10^5,V\leq 10^9\)。
其实类似上面,本质上就是要构造出一个方法使得每种决策被不重不漏的覆盖的一个小根堆。
显然首先给每个数组内部从小到大排序,我们考虑按顺序确定选什么。
比如我们可以定义状态 \((sum,i,j)\) 表示确定了前 \(i-1\) 行,第 \(i\) 行选第 \(j\) 个,且 \(i+1\cdots n\) 行默认选第一个的和是 \(sum\)。
转移:
- 第 \(i\) 行换成选第 \(j+1\) 个,\((sum,i,j)\to (sum+a[i][j+1]-a[i][j],i,j+1)\)。
- 确定了第 \(i\) 行就选 \(j\),换到下一行,\((sum,i,j)\to (sum,i+1,1)\)。
但是这样的状态问题多多啊,虽然确实不漏,但是会算重复。
比如一种决策 \((1,3,2,1,1)\) 可以表示为 \((?,3,2)/(?,4,1)/(?,5,1)\)。
针对这个,我们可以先默认每行的第一个被选,然后将 \((sum,i,1)\) 这种状态去掉,转移到下一行时就直接 \((sum,i,j)\to (sum+?,i+1,2)\)。
那么决策 \((1,3,2,1,1)\) 就只能表示为 \((?,3,2)\) 了,但是诶,这么改又导致漏掉了一些决策,比如 \((1,1,2)\),你从第二行转移到第三行时,根本就没有从 \((sum,2,1)\to (sum,3,2)\) 的情况啊,那么就特殊处理一下这个情况,当 \(j=2\) 时,转移加一个 \((sum,i,2)\to (sum+(a[i][1]-a[i][2])+(a[i+1][2]-a[i+1][1]),i+1,2)\)。
但是 \(a[i][1]-a[i][2]\) 是负数啊,我们要求代价要递增啊,也就是说,我们需要满足 \((a[i][1]-a[i][2])+(a[i+1][2]-a[i+1][1])\geq 0\),整理一下,即 \(a[i+1][2]-a[i+1][1]\geq a[i][2]-a[i][1]\)。
那么我们将这 \(n\) 个数组按 \(a[i][2]-a[i][1]\) 升序排序即可。
什么,你说有数组就一个数,那单领出来即可。
复杂度 \(O(k\log n)\)。
Shopping Plans
- 给定 \(n\) 个数组,每个数组选的数的个数必须位于 \([l_i,r_i]\),求前 \(k\) 小的方案,没有输出 \(-1\)。
其实就是上面两个东西套起来,每个数组看成一个黑盒,里面按照 Shopping Plans on Multiset 得到新的合法方案,外层套 Shopping Plans on Arrays,动态记算,外层需要数时再让内层算即可。
PS:
-
\(l=0\) 时,要将 \(0\) 加入答案,再将 \(l=1\)。
-
\(l=r=0\) 的情况,理性特判。
-
状态转移时的各种边界。
-
ans=A1,Q.push((Node2){ans+a[1].y[2]-a[1].y[1],1,2}); A1 为那些只有一种选择方案的行的和

浙公网安备 33010602011771号