8.23

护照

在第 \(i\) 个点买一张票,就能在 \([L_i,R_i]\) 中任意行走,求从每个点出发,最少买几张票能走遍 \([1,n]\)

tag:最短路,线段树优化建图。

题目的问题是求最少代价,于是我们发现题目很像一个最短路模型:\(i\) 向一个虚点 \(u_i\) 连边权为 \(1\) 的边,\(u_i\)\([L_i,R_i]\) 连代价为 \(0\) 的边。连边看起来很多,但是由于连边连向的是一个区间,我们只需要线段树优化建图即可。

考虑最终的答案是什么:由于连边是连向一个区间,所以对于点 \(i\),所有买的票的并,也就是我们能走到的点,构成了一个大区间。所以走遍 \([1,i]\) 的充要条件是走到 \(1\) 。因为 \(1\) 为该区间左端点。同理走遍 \([i,n]\) 的充要条件是走到 \(n\),因为 \(n\) 为该区间右端点。所以我们建反图并从 \(1\)\(n\) 分别跑最短路,每一个点的答案应该是到 \(1\) 的最短路加上到 \(n\) 的最短路。

然后我们发现这个不对,因为到 \(1\) 的最短路和到 \(n\) 的最短路会有重复的路径,这一段路径只会被计算一次。例如,从点 \(i\) 出发只需要买一张 \(i\) 的票就可以了。

我们如果设初始 \(dis_1+dis_n\) 为初始答案 \(ans_u\),那么我们发现对于任意的 \((u,v,w)\)\(ans_v \le ans_u + w\)。于是我们考虑再次使用最短路,只不过这一次往优先队列中加入所有点作为源点进行松弛。

如果对算法进行思考,我们会发现只需要将所有票的虚点作为源点即可。原因如下:

  1. 对于线段树上的点,它们只负责联通原始图中有边的点,从其出发的边权值都为 \(0\),因此不用松弛。
  2. 对于原有点,我们建的反图中必定有票的虚点连向原有点的边,因此只要不是无解情况,原有点必定会被松弛到。

Election

c,t 组成的字符串,每次询问一个区间,问至少需要删掉多少个 t,才能使在子区间任意一段前后缀里,c 的个数都不小于 t 的个数。

tag:线段树

两类字符比大小,很经典的操作就是把一个视为 \(-1\),另一个视为 \(1\)。这里我们把 c 视为 \(-1\),把 t 视为 \(1\),那么原问题转化为:需要删掉多少个 \(1\),才能使子区间任意一段前后缀和都不大于 \(0\)

考虑先满足前缀,再满足后缀。我们贪心地删除:如果这一个 \(1\) 会使前缀大于 \(0\),我们才将它删除。这不仅用最少的次数满足了前缀都不大于 \(0\) 的限制,还将删除的点尽量右移。这会使后缀的修改更优。

考虑用变量描述前缀需要的最大删除数。我们设变量 \(pre_i\) 表示 \(l\)\(i\) 的前缀和,可以发现当我们修改了 \(pre_i\) 变为 \(0\) 时,之后我们第一个修改的 \(pre_j\) 必定大于 \(pre_i\),且修改的次数变为 \(pre_j-pre_i\)。那么只满足前缀的答案是 \(\max\{pre_i\}\)

现在考虑后缀。后缀的难点在于我们已经提前删除了一部分 \(1\) 了。于是我们考虑删除了多少。对于点 \(i\),之前在满足前缀过程中删除的点为 \(\max\{pre_j\}(j\le i)\),那么之后删除的点为 \(\max\{pre_k\}-\max\{pre_j\}(j\le i)\)。所以若设原序列 \(r\)\(i\) 后缀和为 \(suf_i\),新序列后缀和为 \(new_i\),那么 \(new_i = suf_i - (\max\{pre_k\}-\max\{pre_j\}(j< i))\)

考虑最终答案 \(\max\{pre_i\} + \max\{new_j\}\),把脑子放进垃圾桶,我们来推式子:

\[\begin{aligned} \max\{pre_i\} + \max\{new_j\} = \max\{pre_i\} + \max\{suf_j - (\max\{pre_k\}-\max\{pre_i\}(i< j))\} \\= \max\{pre_i\} - \max\{pre_k\} + \max\{suf_j +\max\{pre_i\}(i< j)\} \\= \max\{pre_i\ + max\{suf_j\}(i< j)\} \end{aligned} \]

发现这实际上就是前缀的最大值加上在它右边的后缀的最大值。用线段树维护即可。

y-fast trie

给出常数 \(C\) 和一个初始为空的集合 \(S\),你需要支持以下操作:

  1. 增加一个数 \(x\),保证 \(x\)\(S\) 中没有出现过。
  2. 删除一个数 \(x\),保证 \(x\in S\)
    在每次操作后,你需要输出 \(\sum_{a,b\in S}(a + b) \bmod C\)。强制在线。
    \(n \le 5e5\)

tag:模型刻画。

首先不难发现取模实际上把答案分成了两种,\(a+b-C\)\(a+b\)

于是把 \(a+b\) 分类。分成 \(a+b\ge C\)\(a+b < C\)。对于前者,我们找出 \(S\) 内的最大的两个数匹配即可。下文着重考虑 \(a+b\) 的情况。

对于 \(a\),发现 \(a+b\) 随着 \(b\) 的增加而增加,直到 \(a+b \ge C\) 为止。所以 \(a\) 会选择其中最大的 \(b\)。一种暴力的想法是,我们对于每个 \(a\),找出使 \(a+b<C\)\(b\) 的最大值。时间复杂度 \(O(qC\log C)\),具体取决于集合内数的个数。

考虑优化这个暴力。对于每个 \(b\),发现 \(a+b\) 随着 \(a\) 的增加而增加,直到 \(a+b \ge C\) 为止。所以 \(b\) 会选择其中最大的 \(a\)。于是 \(a\)\(b\) 就从单项选择变为了双向选择。这是很好的,因为这就意味着所有 \(a\)\(b\) 两两匹配。

考虑维护匹配。对于增加操作,我们找到其作为 \(a\) 能匹配到的最大的 \(b\),如果 \(b\) 没有匹配则可以直接匹配。否则若 \(a\) 比与 \(b\) 匹配的点更优,我们就扔掉 \(b\) 匹配的点,并将其与 \(a\) 匹配。如果匹配更新,则也要与当前答案比较。

对于删除操作,若没有匹配,可以直接删除。若有匹配,我们需要在删除 \(a\) 后另外为 \(b\) 寻找匹配。这是增加操作做的事情。因此我们删去两个数,并再次增加 \(b\) 即可。这些操作都可以使用 set 完成,时间复杂度 \(O(q\log C)\)

意外地难写。

posted @ 2023-08-23 21:18  _maze  阅读(19)  评论(0编辑  收藏  举报