Typesetting math: 100%

【恐怖】【猎奇】【悬疑】Meatherm 大战北京集训

目录

Todo List#

颜色段均摊#

势能分析线段树#

楼房重建 (SGT Beats 做法)#

[SDOI2017] 树点涂色 (代码实现) √#

[Ynoi2007] rfplca 题解#

[CERC2013] ESCAPE 题解#

线段树合并:你真的了解它的复杂度吗?#

树上启发式合并与树链剖分:轻重儿子分开维护的方法#

整体二分:我的二分在你之上!#

[CSP-S 2019] 树的重心:我觉得你不一定还会做!#

CDQ 分治:你真的会它吗?#

优化建图:优化建图?优化建图!#

闵可夫斯基和#

读读这个:[Link](数据结构杂谈 - MLE的博客 - 洛谷博客 (luogu.com.cn)) [Link](求助谷 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn))#

动态 DP#

分治 FFT#

摩尔投票法#

2023.12.25#

今日巨作:数据结构闲谈:范围分治的「双半群」模型#

可以说是写清楚了平时数据结构遇到的一些问题。

Segment Tree Beats 复杂度尚且不清楚,除此之外的部分过于平凡,遂不赘述。

今日阅读:lxl 数据结构体系#

  • P1~P27 写明了各种数据结构能够维护的信息方向。

  • P10 多维空间体系:遇到问题时,我们可以把问题中的元素 / 操作描述为一个多维正交空间的范围。动态问题可以加入时间维,并尝试描述为高维静态问题。然后采用各种方式给问题降维。

    降维方式:

    • 扫描线扫若干维。问题的难度随着扫描线选取维度的不同而不同,因为每一维上的问题可能本质不同。扫一维一般为普通序列扫描线,复杂度线性;扫多维的扫描线(以二维最为常见)复杂度为 O(nmd1d),其中 d 为维度,证明不详。当 d=2 时为普通莫队的 O(nm)

    • 数据结构维护。

      一般需要配合扫描线使用,应当枚举扫描线扫的维度,来确定数据结构应该维护什么维度。

    • 差分。

      以区间为例,[l,r] 若可以差分为 [1,r][1,l1],那么问题维度则减小 1

    • 容斥。

      当条件很强时,时时刻刻都满足条件非常困难,不如考虑某个条件不满足的,再用总方案减去。

  • P28 自由度体系:用来描述元素 / 操作需要的参数个数。通常用来可视化扫描线的方向。

    • 自由度越低,通常对应维护难度也要更小,因为自由度越低,要维护的信息量也越少。

    • 扫描线利用一定代价降低自由度。

    • 差分无代价降低自由度。

    • 静态问题变为动态问题时,比原问题自由度增加 1

  • 树上问题和序列问题的类比:

    • 树分治对应中点分治。点分治因为同时对应了多个分治结构,但中点分治不然,因此点分治 < 边分治。树分治首先考虑边分治是否能做,在优化常数时再切换回点分治。
    • 子树问题 < 序列问题 < 路径问题。
    • 树链剖分:花费 log 代价把路径问题变为序列问题,但不一定一直有效(路径颜色数)。
    • 树上差分:类比 "反向" 序列差分,但不一定一直有效。
    • 启发式合并:树上不删除莫队。
    • 路径莫队:采用括号序转为普通莫队,每个元素加入 / 删除顺序不详,因此需要支持维护的信息构成交换半群。
    • 虚树:暂且不知道如何类比。
  • 几个规约矩阵乘法的问题

    暂略

  • 满点集与散点集的区别:

    满点集:是对整个范围进行操作。满点集平面矩形加法,可以使用树套树等。

    散点集:对于某个范围中存在的点进行操作。散点集平面矩形加法,一般使用 KDT。

    多维亦然。

配合下图食用更佳。

![img](file:///C:\Users\Meatherm\Documents\Tencent Files\2725908309\Image\C2C\26521L%L[VOC@OB1T_OSE%5.jpg)

今日写题#

不写。

今日口胡:[BJOI2019] 删数#

首先考虑对于一个序列如何统计答案。记 cnti 表示出现次数,一个观察是若 i 能删,则下一个能删的数需要是 icnti。于是能够删完的充要条件是取所有 1in,将 [icnti+1,i] 覆盖后,[1,n] 全部被覆盖。

接下来考虑至少修改几个数。修改一个数最多让一个位置被覆盖,答案的一个下界显然是没有被覆盖到的位置数量,同时我们一定能够构造得出这个下界:注意到一定有被重复覆盖的位置,或是在 [1,n] 之外的位置,将这些位置拿过来覆盖即可。

如果没有全局修改,只有单点修改,我们可以容易地维护 cnti,并利用线段树维护出每个位置的覆盖次数。因为覆盖次数最小为 0,于是我们只需要维护出 [1,n] 的最小值以及最小值个数,就可以得出有多少个位置没有被覆盖。

接下来考虑全局加减。我们改为记录全局位移标记 t

最初我们关心 [1,n] 的覆盖情况,若有全局 +1,则 n 变为 n+1,原来的 n 不能被用来覆盖,我们应该删除 [ncntn+1,n] 的贡献。同时,我们只关心 [0,n1] 的覆盖情况了,于是令 t1

若有全局 1,则 n+1 变为 n,原来的 n+1 可以被用来覆盖,我们需要加入贡献。然后令 t+1

另外,因为全局位移标记的存在,若要修改一个数为 x,实则是将它看作 x+t,然后修改。

代码容易实现。

今日口胡:[AH2017/HNOI2017] 影魔#

考虑统计每个位置 k 作为中间的最大值时,对于合法点对 (i,j) 的贡献。找到 sk,tk 使得 skk 左侧最靠近 k 的大于 ak 的位置,tk 是右侧最靠近 k 的大于 ak 的位置。则 i=sk,j=tk 的贡献为 p1sk<i<k,j=tki=sk,k<j<tk 的贡献为 p2

转化为单点加法,行上的区间加法,列上的区间加法,询问矩形 (l,l)(r,r) 的和。

当然可以放成矩形加矩形求和来做,但是可以考虑聪明的:还是考虑扫描线扫掉一行,如果没有列上的区间加法,便可转化为某一时刻的区间加法。我们发现询问矩形是 (l,l),(r,r) 这样的形式,即询问矩形关于 y=x 对称,于是列上区间加法也可以对称成行上区间加法,然后直接做就可以了。

2023.12.26#

今日写题:[SDOI2017] 树点涂色#

首先注意到同一颜色一定构成垂直链,不会拐弯。记 dx 表示根到 x 的路径颜色数,分类讨论 LCA 处的颜色是否和 u,v 相同,可以得出 u,v 路径上的颜色数一定等于 du+dv2dlca(u,v)+1

我们类比一下 LCT,对于 3 操作,可以看作 LCT access,新拉出一条实链。那么原来的 dx 等于 x 到根的轻边条数 +1。使用树链剖分维护即可,若重边改轻则在边的底端的子树 +1,否则 1

实现略有细节(事实上采用了另外一种实现:每次直接将一条实链的 d 值更新为正确的,并在接下来的操作中忽略掉这条实链所在的子树),详见代码。

今日写题:CF768G Winds of Winter#

对于一般的情况,考虑删掉一个点之后,我们比较关心三棵子树的大小:mx,se,mn,分别表示最大的、次大的和最小的子树大小。为了让答案变得更优,我们显然只能让 mx 取下一棵大小为 x 的子树接到 mn 上。最终的答案即为 minx{mxx,mn+x,se}。因为 se 为定值,我们只需要让 x 尽量接近 mxmn2 就可以了。

对于每个点 umx,se,mn 都是好求的。但是怎么求出 x 的集合呢?我们发现,mx 对应的连通块要么来源于 u 的重儿子构成的子树,要么来源于整棵树去掉 u 的子树构成的树(即 u 的外子树)。

对于外子树可以使用 set 维护,set 中存储当前点的 size。但是在 u 到根路径上的点,它们的 size 在 u 被删除后会减少 sizeu。于是我们需要维护两个 set,一个存储在 u 到根路径上的点的 size(不包括 u),另一个存储不在 u 子树内,也不在 u 路径上的点的 size。对于第二个 set 可以直接查询。对于第一个 set,查询时 set 中大小为 x 的元素实际大小为 xsizeu。我们还要维护重儿子的 set,这个可以直接 dsu on tree 得到。

对于第二个 set,我们采用以下方式维护:进入一个点 u,就在第二个 set 中删除它。但是在先后遍历 u 的不同子树 a,b 时,进入 ba 子树中所有点应该存在于 set 中,而我们已经删除了它。于是我们需要暴力加入 a 中所有点。

这样的时间复杂度是错误的,但是我们发现对于最后一棵子树我们不需要这么做。于是我们把重儿子放到最后一棵子树处理,这样复杂度仍然是 dsu on tree + set 的 O(nlog2n),可以通过本题。

今日口胡:区间本质不同子串个数#

考虑静态区间颜色数,我们可以离线下来扫描线,对于每一种颜色我们记录 lst,扫到 r 的时候查询有多少种颜色的 lst 大于等于 l

对于本质不同的字符串,我们可以类比,扫到 r 的时候把 s[1...r] 的所有后缀的 lst 更新。对于一个后缀 t,它的 lst 应该被更新为 r|t|+1

考虑怎样得出 s[1...r] 的所有后缀。对 s 建立 SAM,找到 SAM 上代表 s[1...r] 的前缀节点 x,那么 x 和它在 Parent 上的所有祖先对应的字符串构成 s[1...r] 的所有后缀,我们现在要更新这些字符串的 lst

这很像 LCT 的 access 操作。对于每条被经过的实链,记录下它上一次被更新的位置,设其为 r,以及这条实链上对应的字符串长度 [lenl,lenr]。那么 [rlenr+1,rlenl+1] 是原来这些字符串的 lst,应当删去,于是对应上了线段树上的一次区间修改。

最后,Parent 树的根节点 1 到节点 x 组成了一条新的实链,那么 [1,r] 是这些字符串现在的 lst,在线段树上加入贡献即可。

时间复杂度 O(nlog2n+mlogn),利用树链剖分 + 颜色段均摊分析可以达到同样的复杂度。不会 LCT,遂不实现。

今日口胡:[NEERC2015] Distance on Triangulation#

这是一个三角剖分图。考虑 u,v 之间的最短路一定经过一条对角线,使得将多边形按照对角线分开后,uv 分居对角线两侧(或位于对角线上)。我们每次枚举一条对角线,计算在 u,v 在其两侧的询问。具体方法是从对角线出发 BFS,求出每个点到对角线两个端点 (p1,p2) 的距离,然后分类讨论路径怎样跨过对角线(只经过 p1,p2 中的某个,或是经过 (p1,p2) 这条边),求最小值。

将原图转对偶图,容易发现变为度数不超过 3 的树,对该树进行边分治时间复杂度正确。考虑对于原图进行边分治等价于选取一条对角线,分开多边形进行分治,因此对于原图,选取适当的对角线进行分治的复杂度正确。

时间复杂度 O(nlogn),代码繁杂,遂不实现。

今日题解:[Ynoi2004] rpmtdq#

看到 li<jr 考虑求支配点对。对于路径考虑点分治。容易发现不需要考虑不在同一子树的限制,这样 di+djdis(i,j),而取 min 会把这个东西支配掉。

我们考虑在这一层,怎样的点对 (i,j) 不会被 (i,k)(k,j) 支配掉,其中 i<k<j。事实上,如果 min(ak)max(ai,aj),那么 (i,j) 就会被支配掉。钦定 ajmax(ai,aj) 进行考虑,那么 aj 只能和它左侧第一个小于等于它的 i 组成支配点对。钦定 ai 时对称考虑。使用单调栈解决。

这样总共会有 O(nlogn) 个支配点对 (i,j)。离线扫描线,当扫到 j 时,所有左端点在 ii 左侧的询问的答案都会对 dis(i,j) 取 min。使用树状数组,转化为前缀取 min,求后缀 min,时间复杂度 O(nlog2n+mlogn)

2023.12.27#

今日口胡:[JOISC2020] 首都#

一个很直接的想法是对于每种颜色建立虚树,然后计数虚树上的不同颜色数,最后取 min 得到答案。虚树颜色数比路径颜色数更强,而前者已经需要用到 O(nm) 级别的树上莫队。

事实上这种想法还是过于傲慢了。我们只需要求出一个全局的答案,而枚举颜色这个行为本身是强化了这个问题。不妨多考虑颜色之间的关系。

  • 点分治

    我们只关心最后的连通块。连通块可以经过当前分治中心,也可以不经过。点分治,考虑形成包含当前分治中心的连通块的最小操作次数。取分治中心为分治范围的根并将分治范围中所有和它颜色相同的点加入队列。处理队列时,每一次取队头的点,如果当前点的父亲没有被加入过队列,则把当前点的父亲以及分治范围中所有和当前点父亲颜色相同的点加入队列。最后队列中出现过的颜色数就是答案。

    事实上对于一种颜色,如果它在分治范围外有点,那么只考虑分治范围中的点是错误的,因此如果处理某种颜色时,它不满足这种颜色的所有点都在分治范围内,那么就找不到包含当前分治中心的连通块,直接停止这个过程,并递归。

    时间复杂度 O(nlogn)

  • 建图

    我们发现,如果两个颜色 i 的点路径上经过了一个颜色 j 的点,那么要取颜色 i 的点在连通块中,就必然要取颜色 j 的点在连通块中,于是建边 ji

    找出颜色 i 的点构成的虚树,则上述过程可以使用线段树优化建图来实现。建图完成后使用 Tarjan 缩点,点数最小的出度为 0 的连通块的大小即为答案。

    时间复杂度 O(nlog2n)

点分治做法代码较为平凡,建图做法代码较为繁杂,遂不实现。

今日口胡:XSY1642 Another Boring Problem#

树上莫队,转化为 O(nm) 次插入 / 删除,求集合 k 大。使用 O(1) 加入,O(n) 查询的分块解决,时间复杂度 O(nn+nm)

代码较为平凡,遂不实现。

今日口胡:「2021 集训队互测」Lovely Dogs#

我们考虑 fd(x) 的含义:若 x 有一个 pd+1 次的因子则为 0,否则为 (1)c(x),其中记 c(x)x 的质因子数量。

我们尝试分离 fd(ij)。首先,在 d 充分大的时候,我们直接有 fd(ij)=fd(i)fd(j)。考虑当 d 缩小时,fd(ij) 会怎样被影响:

  • d 的缩小直接使得 fd(i)fd(j) 变为 0,即 ij 本身便有一个 pd+1 次的质因子。于是我们直接不需要考虑这样的 ij 对其它数的贡献。
  • d 的缩小没有影响到 fd(i),fd(j),但是 ij 有一个 pd+1 次的质因子。记 h(t) 表示最大的数 p 使得 pd+1t,那么 fd(ij)=fd(i)fd(j)[h(ij)=1]

对于第二类情况,我们发现可以对其施以莫比乌斯反演:

fd(ij)=fd(i)fd(j)[h(ij)=1]=fd(i)fd(j)kh(ij)μ(k)=fd(i)fd(j)kd+1ijμ(k)

我们还需要最后一步:把 kd+1ij 中的 ij 分离出来。事实上若 ki,要使 kd+1ij 则必然有 kkgcd(k,i) 使得 (k)d+1j,而我们不需要考虑这样的 j 的贡献。于是我们只需要枚举 ki,上式变为

fd(i)kiμ(k)kd+1gcd(kd+1,i)jfd(j)=fd(i)kiμ(k)j[kd+1gcd(kd+1,i)j]fd(j)

大功告成。

我们接下来考虑询问。直接通过 DFS 序转化为区间询问,可以通过莫队解决。加入一个数 i 时,考虑它和之前的数构成的贡献,若 fd(i)=0 我们不做任何操作。其它情况下,我们可以通过上面的式子,枚举 k,然后计算所有满足 kd+1gcd(kd+1,i)jfd(j) 之和。我们维护 sum 数组,每加入一个 j,就把 j 的所有因数处的 sum 加上 fd(j),这样便可以 O(1) 求得最里层和式的值,从而 O(d(i)) 地求得整个和式的值。删除一个数的贡献和加入时的情形几乎相同,除了要取负号之外。

我们发现使用莫队维护时,指针移动一次的代价不是相同的。但因为子树转为 DFS 序区间后,每个区间的左端点都不会相同,因此一个块中只会有 O(n) 次询问,因此左端点只会经过每个位置 O(n) 次,右端点每块只会经过每个位置 O(1) 次,因此每个点的总经过次数为 O(n),总时间复杂度为 O(nlnnn)

但同时我们发现 DFS 序莫队是多余的。树上的不删除莫队应当是 Dsu on Tree,此时时间复杂度变为 O(nlnnlogn)=O(nlog2n)

Qiuly 老师说的非常好。一看到 a 构成排列便应当警觉起来,因为这蕴含着调和级数 HnO(nlnn)。调和级数会使得一些在极端情况下退化为暴力的算法在 Hn 的保护下变成正解。

此题中构造处 h 函数然后施以莫比乌斯反演的技巧也颇为有趣。一个有趣的事实是,μ(ij)=μ(i)μ(j)μ(ij)2,而本题中有 fd(ij)=fd(i)fd(j)fd(ij)2,我们构造 h 函数的灵感恐怕就是来源于此。

代码较为平凡,遂不实现。

今日写题:CF888G XOR-MST#

最小生成树的两种思路可以变成不同的两种解法:

  • Kruskal

    建出 Trie,尝试使用 Kruskal。首先找出所有边权为 0 的边,这等价于对于点权进行 unique。接下来考虑边权为 1 的边,即 Trie 中倒数第二层节点的两棵子树之间的相互连边。如果两棵子树里都有点,那么连这些边一定不劣。接下来考虑倒数第三层节点,如果它的两棵子树里都有点,那么这两棵子树里的点在上一轮中一定已经连通,并且这两个连通块都需要往外连边。如果它们不选择相互连边,下一轮的边权一定更大,于是这两个连通块之间一定会相互连边。

    枚举一棵子树中的点,在另一棵子树上进行 Trie 贪心,取最小值即为答案。注意到一个点最多会在 O(logV) 轮中被枚举,每次贪心的复杂度为 O(logV),于是总复杂度为 O(nlog2V)。若改用启发式合并,则总复杂度变为 O(nlognlogV)

  • Boruvka

    直接做即可。建出全局 Trie,维护当前连通块的 Trie,对于每个连通块,枚举点,然后在全局 Trie 和当前连通块的 Trie 之差上进行 Trie 贪心,找到最小出边。Trie 合并类似线段树合并,是 O(nlogV) 的,而 Boruvka 算法最多进行 O(logn) 轮,于是总复杂度为 O(nlognlogV)

今日口胡:Rectangle (Source: 2023.12.14 树上问题)#

Boruvka。每一轮进行一次扫描线,维护一行的 min,以及和 min 颜色不同的 min,可以轻松找到当前连通块的出边。

注意到 Ai,j 不一定等于 Aj,i,找到的不一定是真正的最小出边。可以再换维扫一遍,维护某一列的 min 即可。

今日口胡:CF632F Magic Matrix#

前两个条件容易判断。满足前两个条件后,可以将该矩阵看作一个邻接矩阵。令 bi,j=mink{max(ai,k,ak,j)},则一定有 bi,jai,j,同时题目要求 ai,jbi,j,于是该矩阵合法当且仅当 bi,j=ai,j。我们可以改写 ai,kminl{max(ai,l,al,k)},从而使 bi,j=mink{max(minl{max(ai,l,al,k)},ak,j)}。不断改写,我们可以发现 bi,j 的含义为:ij 的所有路径中,最大边最小的路径的最大边长度。若 bi,j=ai,j,则含义为最大边最小的路径的最大边长度为 ai,j,换言之,仅保留 <ai,j 的边时 i,j 不连通。

可以跑一遍最小生成树,从小到大枚举权值,检查这种权值的每一条边的两个端点是否在之前就连通。若是,则不符合条件。

另一个惊世骇俗但相当好想的做法是:条件 3 等价于对于第 i 行和第 j 行,每一列至少有一个数大于等于 ai,j。将 <ai,j 看作 0,其余看作 1,那么条件等价于 i,j 两行 OR 后全为 1

离线处理询问。将所有 ai,j 排序后从小到大扫描线,在 bitset 中修改。因为每个位置只会由 10 一次,因此瓶颈仅在于查询的 O(n3ω)

2023.12.28#

今日口胡:LOJ NOI Round 2 黄金矿工#

depx 表示节点 x 的深度,若矿工 x 和黄金 y 匹配则价值为 vx+vy+(depydepx)=(vxdepx)+(vy+depy)。可以按照上式改写 v,然后矿工和黄金匹配的价值变为二者价值之和。

此时可以考虑模拟费用流。对于节点 x 上价值为 v 的矿工建立 (S,x,1,v),节点 x 上价值为 v 的黄金建立 (x,T,1,v),树上父亲向儿子建立 (fa,i,,0),然后跑费用流。

若加入矿工,则决策有三种:

  • 直接匹配:找到子树中没有被匹配过的黄金进行匹配。

    我们的网络可以直接处理。

  • 交换匹配:让祖先的某个矿工去匹配一个新的黄金,自己来匹配该矿工原来匹配的黄金。

    我们的网络可以按照以下方式处理:沿着容量 1 的反向边向上走,然后找到某个点子树中没有被匹配过的黄金进行匹配。走网络流中的反向边实质上交换了匹配。

  • 移除匹配:让祖先的某个矿工移出匹配,自己来匹配该矿工原来匹配的黄金。

    我们的网络没有办法处理这种情况。因为前两种情况都属于增广,会让流量 +1。为了应对这种情况,我们需要退流,复杂度无法估量。考虑反悔贪心的思想,我们不退流(即:显式对决策进行反悔),而是新增一些黄金来表示反悔。具体来说,如果节点 x 上一个价值为 v 的矿工被加入匹配,而它之后需要被反悔(移除匹配),那么就在节点 x 上加入一个价值为 v 的黄金。

同理,若加入黄金,我们沿反方向经过网络中的边,则决策亦有三种:

  • 向上走,直接匹配。
  • 向上走若干步,然后向下走到某个点上,需要保证向下走的若干步中,反向边容量均 1,然后匹配。
  • 移除匹配。同理,这里我们的解决方法是,若黄金被加入匹配则在节点上加入一个价值为 v 的矿工。

我们通过加入黄金 / 矿工的方法移除了决策三,那么对于节点 x 上的矿工,可匹配范围是 q 子树中的黄金,其中 qx 的祖先且 xq 路径上边的容量均 1。每个节点维护可用黄金构成的 multiset,然后线段树修改 / 查询即可。

对于节点 x 上的黄金,可匹配范围变得非常奇怪:x 到根路径上的每个点 p 的子树中满足 qp 路径上边的容量均 1 的点 q 构成的集合,但同时这样的 q 又不能在 x 的子树内。

于是考虑重链剖分,每个节点维护自己和轻儿子的子树中合法节点构成的集合中价值最大的矿工,然后跳重链查询即可。

接下来考虑加入和删除的影响。

  • 只加入黄金,不进行匹配,则直接在对应点的 multiset 中插入,然后更新线段树上的信息即可。
  • 只加入矿工,不进行匹配,先在对应点上的 multiset 插入,如果比当前点上最大值要大,就跳重链,每次跳到重链头的父亲处就更新它的信息,直到出现 0 边为止。找到最深的 0 边可以通过线段树维护区间中最靠右的区间最小值位置来实现。
  • 加入矿工后找到原有黄金进行匹配。这种情况下加入的矿工不会被放到 multiset 里面。我们会在黄金所在的 multiset 中进行删除,然后更新线段树上的信息。
  • 加入黄金后找到原有矿工进行匹配。首先在 multiset 中删除矿工,然后向上跳重链,在该矿工有贡献的点上消除贡献,并加入新的贡献。

操作 3,4 结束之后还存在一次 lca(u,v)u 的反向边容量加法,lca(u,v)v 的反向边容量减法。考虑如何实现:

  • 对于反向边容量加法,若链头到链头父亲的边原来容量为 0,则现在容量为 1,故应当加入贡献到链头父亲处。
  • 对于反向边容量减法,若链头到链头父亲的边原来容量为 1,则应当直接删除贡献;否则考虑重链中最浅的一条 1 边,只有这条 1 边之上的部分才能进行贡献了,因此应当删除原有贡献并更新为现在的贡献。

代码极为困难 (5.8k),遂不实现,且近期不打算实现。

本题不失为一道模拟费用流好题。然而经此一题仅能窥得模拟费用流算法的冰山一角,若有空可详细阅读:模拟费用流小记

今日口胡:最小费用最大流的 SSP 算法 正确性证明#

考虑费用流函数 ff,其中 f,f 流量相同,但 f 费用更小。令 Δ=ff,则 Δ 上存在若干圈。且因为 f 费用更小,因此 Δ 上至少存在一个负圈,故 f 不为最小费用流函数。

因此,费用流函数不为最小费用流函数,则残量网络上存在负圈。该命题的逆否命题为,若残量网络上不存在负圈,则费用流函数为最小费用流函数。

考虑归纳证明。根据 SSP 的适用条件,图中初始不存在负环,则 f0=0。考虑当前的流函数 fi 沿着 st 的最短路增广 1 的流量得到 fi+1,若有 fi+1 满足费用 <fi+1,那么考虑 fi+1fi,一定是若干个环和一个 st 的路组成的。

完蛋,编不下去了。弃疗

今日写题:LOJ6089 小Y的背包计数问题#

很显然考虑到根号分治,令 m=n,称 m 的部分为小的部分,跑多重背包 + 前缀和优化,时间复杂度 O(nn)。对于大的部分,注意到一定不会用完,于是等价于完全背包。

沿用 NOI Online 跑步那道题的 Trick,选若干个大的可以被以下两种操作表示出来:

  • 加入一个新的 m+1
  • 给全体数都 +1

g(i,j) 表示选了 i 个大的,当前和为 j 的方案数。g(i,j)=g(i1,j(m+1))+g(i,ji),同样是 O(nn) 的。

最后将两部分卷积,容易发现我们只需要求卷积的一项,因此统计答案 O(n)

今日写题:[BalticOI 2022 Day1] Uplifting Excursion#

首先判断无解的充分条件:选全部负数仍 >L,或选全部正数仍 <L

对于其它情况,首先考虑全部选上。如果全部选上的和 <L,那么就把所有数取相反数,即 L 变为 L,同时翻转 a 数组。

这样一定有全部选上的和 L。一种经典的思想是贪心 + 小范围微调,即贪心地把最大的数移除选中的集合,让和落在 [L,L+m) 这个区间内,再考虑怎么调整为 L。设最终落在 L[L,L+m) 处,那么不难发现,此时的方案一定是所有和为 L 的方案中,保留了最多数的方案。

考虑对这个集合微调使得和最终为 L。我们会删除一些数,并加入一些数。注意到每次变化量的绝对值不超过 m,于是最终方案一定能够安排某种顺序(事实上就是若和 >L 则删除正数 / 加入负数,反之同理),使得和始终落在区间 [Lm,L+m] 内。

接下来需要一点有人类智慧的观察。如果我们在调整的过程中的某两个时刻使得集合的和相同,且第二个时刻的集合大小不大于第一个,那么显然不优;同时第二个时刻的集合大小也一定不会大于第一个,这样我们从第二个时刻开始倒序进行操作,会使得初始和为 L 的方案中集合大小增大,而这显然和上文中提到的性质矛盾。

因此我们最多调整 2m 次,考虑到上界为选 2mmm,因此只需要进行值域范围为 O(m2) 的多重背包,最后取得 fLL 并加到原有答案之上即可。使用单调队列优化,时间复杂度 O(m3)

另外,要注意加入多重背包的物品范围。所有已选择的数 i 都应当被看作一个体积 i,价值 1 的物品,表示删除。

几点启发:

  • 贪心 + 小范围微调的大范围背包思路。这种减小背包范围的方法似乎还不少见,据我所知__去年 THUPC 初赛的某个题也是这个__,另外可以阅读各种奇怪的背包
  • 对于满足支配性的 DP(取 max,取 min),我们的 DP 并不需要真的找出所有方案然后对这些方案计算。我们可以先分析出某些方案一定不优(例如本题中的大于 2m 次调整),然后只计算可能成为最优解的方案。

今日写题:[THUSC2016] 成绩单#

f(l,r) 表示删光区间的代价,g(l,r,mn,mx) 表示区间 [l,r] 中删到剩下的最小值为 mn,最大值为 mx 的最小代价。

如果 ar 提前删除,那么对于 lk<rg(l,r,mn,mx)g(l,k,mn,mx)+f(k+1,r) 取 min。

如果 ar 不提前删除,采用填表法就直接傻眼了:原来的最小值为 mn 多少才可以转移到最小值为 mn=min(mn,ar)

填表法不方便的原因是取 min / max 是半群信息,不支持很方便地撤销贡献。但是加入一个数后求得新的 min / max 是简单的,于是考虑刷表法。

加入 ar+1,则 g(l,r+1,min(ar+1,mn),max(ar+1,mx))g(l,r,mn,mx) 取 min。

这样计算到 g(l,r,mn,mx) 时第二种转移已经计算过了,就不用管了。

知道所有 g(l,r,mn,mx) 后求出 f(l,r) 是简单的。对 ai 进行离散化,时间复杂度 O(n5),空间复杂度 O(n4)

今日数学:具体数学第三章#

非常困,不妨看点这个。

更新在另外一篇文章里面。

2023.12.29#

今日写题:[AGC035D] Add and Remove#

我们隐约感觉每次删的数 x 会对答案造成 kx 的贡献,但是怎么描述出这个 k 太痛苦了。

不难发现最后剩下 a1,an。不妨倒着考虑:最初只有两个数 a1,an,在中间插入一个数 x 的时候,它会对 a1,an 造成 x 的贡献,于是它被贡献了 2 次。如果在这个数左边插入一个数 y,它会对左右两侧都造成贡献,因为它左侧是 a1,右侧是 xx 会被贡献 2 次,因此 y 会被贡献 3 次。

想到这里就好 DP 了:设 f(l,r,kl,kr) 表示当前要往 (l,r) 中间插入数字,左端点的贡献倍率为 kl,右端点的贡献倍率为 kr,给 (l,r) 中插满数后的最小代价。

枚举当前插入了哪一个数 l<p<r,可以从 f(l,p,kl,kl+kr)+f(p,r,kl+kr,kr)+ap(kl+kr) 转移过来。直接搜索即可,时间复杂度有 T(1)=1,T(n)=2i=1n1T(i)疑似是 O(3n) 的。

今日写题:[AGC039E] Pairing Points#

环是一个非常精密的结构。如果可以的话,我们更乐意把环变成链,这样就可以通过区间来进行考虑了。

n2nn 很小,这允许我们枚举很多东西。我们枚举和 n 相连的点 k。不难发现 [1,k1],[k+1,n1] 这两部分会有一些连边在内部,同时因为连边要构成一棵树,所以它们中要有至少一条相互连边,跨过 (k,n) 这条边。

注意到三点不共线,跨过 (k,n) 的连边若交叉,则一定成环。因此如果连边分别为 (a1,b1),(ak,bk),满足 a1<a2<<ak,则一定有 b1>b2>>bk

我们总是可以在 [1,k1] 中找到一个长度为奇数的前缀 [1,x],在 [k+1,n] 中找到一个长度为奇数的后缀 [y,n1],使得其中各有一个点 p,q 互相连边,其余连边在区间内部,并且至少有一条连边跨过 (p,q)

此时考虑问题被怎样划分:[1,x]p 不能连边,其它点连边至少有一条跨过 p[y,n1]q 不能连边,其余点连边至少有一条跨过 q[x+1,y1]k 不能连边,其它点连边至少有一条跨过 k

至此可以观察到,问题被写作了可以递归的形式。开始设计状态:设 f(l,r,k) 中表示 [l,r] 中除了 k 点不连边(或:k 点连边连向区间外部)之外,[l,r] 中的点相互连边,且至少有一条跨过 k 的方案数。

则考虑枚举 l,r,k,x,y,p,q,有转移 f(l,r,k)f(l,r,k)+f(l,x,p)f(y,r,q)f(x+1,y1,k),此时时间复杂度 O(n7),带一个常数 17!,可以通过本题。

观察转移发现许多东西是无关的:枚举的 x,yk 是独立的, 于是枚举 l,r 之后,我们可以枚举 x,y,p,q,算出 f(l,x,p)f(y,r,q) 之和,然后再在 [x+1,y1] 范围内枚举 k 转移,此时时间复杂度 O(n6)

进一步地,对于 q,我们不关心具体是哪个 p 和它连了边,而只需要知道区间中存在这样一个点就可以了。于是设 g(l,r,k) 表示区间 [l,r] 中有一个点没有在区间内连边,而是连向了大于 r 的某个点 k,除此之外 [l,r] 中的点相互连边,且至少有一条跨过该边的方案数。

那么内层只需要枚举 x,y,q,原来的 f(l,x,p)f(y,r,q) 之和就变为了 g(l,x,q)f(y,r,q),时间复杂度 O(n5)

g 的转移也是简单的。对于边界,有 g(i,i,j)=[i<j][Ai,j=1]。同时 f(l,r,k) 可以贡献到所有满足 k>k,Ak,k=1g(l,r,k) 上。

我们的妙手从第一步就开始了:拿掉和 n 相连的边,让问题变到链上,然后考虑和这条边相连的边的性质,最后发现递归结构。最后分析枚举变量独立,然后降低复杂度的技巧也颇为有趣。

2023.12.30#

今日写题:CF1517F Reunion#

首先期望转计数。不妨将有空的志愿者所在的点称作黑点,没空的志愿者所在的点称作白点。不难发现,答案 r 当且仅当存在一个点 x,使得距离 x 不超过 r 的点中没有白点。同时我们只需要考虑计数答案 r 就够了,差分会给出答案 =r 的答案。

关于「存在」和「对于任意」的计数可以相互转化。在本题,我们尝试向「对于任意」转化,因为这将合法的方案刻画为树上每个节点都需要满足的性质。当然,Alex_Wei 的题解也指出,不进行计数转化也可以求解

上面条件的补集是简单的,即:对于任意点 x,距离 x 不超过 r 的点中有白点。进一步转化为将距离白点不超过 r 的点覆盖后,树上不存在没有被覆盖的点。

据此可以开始 DP。考虑一棵子树中的点,有三种状态:

  • 全部被覆盖,并且还可以往子树外再覆盖 d 的距离。
  • 存在一些点没有被覆盖,并且还可以往子树外再覆盖 d 的距离。
  • 存在一些点没有被覆盖,并且不能往子树外覆盖。

我们考虑第二种状态和第三种状态。它们真的有区别吗?如果子树内有一些点没有被覆盖,那么就算它们能够往外覆盖,这些没有被覆盖的点还是需要被另外一棵合并过来的子树覆盖掉。既然如此,这棵子树往外覆盖就没有任何意义了。

因此我们简化状态为两种,即全部被覆盖和没有全部被覆盖。前者我们关心能往子树外覆盖的距离,后者我们关心最深的没有被覆盖点的深度。设 f(i,d),若 d>0,则表示 i 的子树内全部被覆盖,且可以往外覆盖 d 的距离的方案数;否则表示 i 的子树内最深的没有被覆盖的点深度为 di 的深度为 1)的方案数。

考虑转移。我们初始有 f(i,1)=1,f(i,r)=1。当合并子树 j 时,对于 f(i,x),f(j,y),转移如下:

  • x+y0 则子树内全部被覆盖,对 f(i,max(x,y1)) 贡献 f(i,x)f(j,y)
  • 否则对 f(i,min(x,y1)) 贡献。

最后,我们得到的 i0f(1,i) 即为答案 <r 的方案数 ansr。不难发现答案等于 r 的方案数即为 ansr+1ansr,例外是树全为黑的情况,它不会被计数进去,应当单独统计它的贡献 n

考虑时间复杂度。每一遍 DP 看似是 O(n3) 的,但我们可以用树上背包的方式来分析:首先有 i 子树深度不超过 i 的子树大小,因此:若 d0,则 d 只可能取 r,r1,,rsizei;若 d<0d 只可能取 1,2,,sizei。于是对于每一个 i,可能的状态数都只有 2sizei 个,因此可以套用通用的树上背包复杂度 O(n2),总复杂度为 O(n3),可以通过。

今日写题:[AGC034E] Complete Compress#

n 很小,考虑枚举最后汇聚到的点,作为根。设 sum=colu=1dis(u,rt),那么一次操作要么让 sum2(取不同子树中的两个点),要么让 sum 不变(取同一子树中的两个点)。

第二种操作是对答案没有影响的,于是可以统统不做。对于只剩下一种操作的情形,我们可以看作有 k 个非负整数,每次选两个不同的数出来 1,问最后能不能全部变成 0。答案也很显然,如果这 k 个数中最大的数 mx 满足 summxmx 则且 sum 是偶数则有解,否则无解。

但事实上 mx 是可以被在 mx 所在子树的操作所变小的。于是我们想求 fx,表示 x 子树内最多能操作多少次。

那么求 fx 就和上面的过程差不多了:如果 summxmx,那么可以操作 sum2 次,否则操作次数取决于 mx 所在子树的 f 值,可以递归处理。

最后只需要判断根节点是否有 frt=sum2sum 是偶数即可。

时间复杂度 O(n2),换根 DP 做到 O(n) 是容易的。

今日写题:[Ynoi2006] spxmcq#

询问能够离线,并且有某种单调性,这启发我们从小到大对 x 扫描。设点 u 的答案为 fu,于是有 fu=au+x+vson(u)max(0,fv)。不难观察到 fu 关于 x 单调,于是一定存在某一时刻 fv 变为大于等于 0,从此 fu 对于这个 max 的决策一直是 fv 而非 0。这个过程可以看作:初始每个点都是独立的,接下来随着 x 的增大会有若干次连边,每个时刻,每个点的最优决策都是取子树内和自己在一个连通块的点。

维护 u 子树内连通块的点数 sz 和点权和 sum,当 szx+sum0xsumszfu 变为大于等于 0,此时 u 应当向 fu 连边。

使用堆维护 sumsz,每次找出小于等于 x 的元素,如果对应的点 u 没有连边则进行连边。每次连边后,u 会对 fu 到当前连通块根 rt 这一段的点造成更新,对点数和点权和的贡献大小分别为 szsum。如果 rt 被更新后满足连边条件,则继续连边,直到 rt 不满足条件,此时将新的 sumsz 加入堆中。我们不需要删除 rt 原来的 sumsz,因为 rt 新加入了一部分和大于等于 0 的点,这只会使得 frt 更快地变为大于 0,于是原来的 sumsz 一定更大。

考虑更新时使用的数据结构。我们仅需要支持路径修改 + 单点查询,树上差分后使用树状数组即可。

时间复杂度 O(nlogn)

2023.12.31#

今日模拟赛 (杭二多校):100 + 99 + 70#

题解待补

2024.1.2#

今日写题:CF1456E XOR-ranges#

今日写题:CF1158F Density of subarrays#

题解待补

2024.1.3#

今日写题:CF1466H Finding satisfactory solutions#

今日写题:[APIO2016] 烟火表演#

考虑朴素 DP。设 fu(x) 表示 u 的子树内全部统一到 x 时刻点燃的最小代价,则合并子树 v 的时候有转移

fu(x)fu(x)+min0yx(fv(y)+|wx+y|)

后面一项由 |w(xy)| 变过来。

看到绝对值函数,我们可以大胆猜测 fu 是下凸的。考虑归纳,假设 fu 的所有儿子 fv 均是下凸的,则考虑贡献函数 Fv(x)=min0yx(fv(y)+|wx+y|)。只要我们能够证明,每一个 Fv(x) 都是下凸的,那么 fu(x) 将会是若干个下凸函数的和,仍然是下凸的。

接下来考虑 Fv(x) 的取值。设 Fv(x) 取得最小值的区间为 [L,R],那么:

  • x<L 时,fv(x) 是单调减函数,且容易观察到 fv(x) 的斜率为 1 或更小。因此改小 y 要么使绝对值和 fv(y) 都变大,要么使绝对值变小,但 fv(y) 一定会变大更多,都不优秀。因此转移点只会取 x,且有 Fv(x)=fv(x)+w
  • L+wxR+w 时,我们不需要任何修改就可以取得最小值,因此有 Fv(x)=fv(L)
  • x>R+w 时,fv(x) 是单调增函数,和第一种情况同理的。

编不下去了

另一种更直观的理解方式是:闵可夫斯基和。

2024.1.4#

今日模拟赛 (北京多校):20 + 0 + 44#

T1 sing#

我们发现正着做的时候,决策几乎是不可阅读的。考虑倒着思考,对于最后一个人,它肯定会删掉自己评价值最小的物品。对于前面的人来说,最后一个人总是会删除掉它评价值最小的物品,于是前面的人都不会去考虑这个物品,因为它早晚都会被删除。以此类推,我们得到一个强力的结论:答案是从最后一个人开始,依次删除自己评价值最小的物品,最后剩下的物品。

这样看来,想到这个结论也没有想象中那么困难。不过实际上上面的想法逻辑并不严密,只是我们一厢情愿的设想罢了。不得不承认,OI 有时确实需要猜结论,但是对结论给出严格证明是更为重要的。因为决策具有很明确的阶段性,于是我们运用归纳法就可以了。考虑有 n=1 个人时,上述策略是成立的。接下来,我们尝试从 nn+1 归纳。

对于第一个人而言,如果后面 n 个人都按照上述策略进行操作,那么最后会剩下两个物品 a,b。不妨设第一个人对物品 a 的评价值小于物品 b

  • 如果第一个人选择拿物品 b,那么显然不如拿物品 a,因此一定不优。
  • vk 表示第 k 个人拿走的物品编号。如果第一个人选择拿走 vk(k>1),根据归纳假设:[k+1,n+1] 的决策不变。第 k 个人可能会选择拿走物品 x(x{a,b,v1,v2,,vk1})。不难发现如果第一个人调整为拿 x,第 k 个人保持决策 vk(1,k) 的决策是相同的。因此我们可以不断调整,直到 x 变为 a,b 中的一个。

对于 p 变化的部分是简单的。记 wk 表示当前 k 拿走了评价值第 k 小的物品。考虑 pp1 变化为 p 的过程中,原来 p1 是最后一个取物品的(因为我们倒序开始),现在变为第一个,则 wp1 置为 1,并率先拿走 p1 的评价值最小的物品。这一决策会依次影响 wp2,wp3,,w1,wn,wn1,,wp,但是不难发现,后者均只会变大。运用均摊分析,每次将 wk 置为 1 增加 O(n) 的势能,操作不会进行超过 O(n) 此,同时总势能不超过 O(n2),因此时间复杂度位 O(n2)

T2 count#

k个独立同分布随机变量的最小值的期望。考虑 n 个独立同分布的,[0,1] 间随机变量的最小值期望为 1n+1

因此我们可以使用这个方法来估计答案。每读入一个数据,就使用该数据为随机种子生成伪随机数,来进行 [0,1] 间均匀随机的离散模拟,最后取所有数据随机结果的最小值。这样,相同的数据只会对随机结果贡献 1 次。如果最后的随机结果为 x,那么不同数的个数大概率是 1x1

注意到我们的离散模拟不需要精度太高,unsigned int 范围(大概 109 的精度)就足够了。我们有 256 位的存储空间,为了减少误差,可以将其分为 832 位的 unsigned int,进行 8 次采样取平均值。

注意到误差更小的平均值应当是将 8 次采样的最小值平均,而不是计算 8 次答案后平均,后者无法通过本题。

T3 measure#

考虑 T 是相同的!于是我们的策略就是,按照 imodm 分组,分组相同的使用同一个机器。

对于单组考虑。令 si 表示组内第 i 个人实际开始工作的时间,ti 表示组内第 i 个人到的时间(已排序)。有 si=maxji{tj+(ij)T},于是 i 的等待时长为 maxji{tjjT}+iTti。如果没有修改,那么直接扫一遍维护 tjjT 的前缀最值即可。

注意到后面的部分只和 i 相关,可以单独处理。接下来只考虑维护前面前缀最值和的部分。

我们考虑修改,本质上就是某个元素删除,然后扔到后面去。考虑它对分组的影响,某个区间中的元素全部被扔到上一个分组中去了。于是现在实际的一个组分为三个部分:原来该组的一个前缀,另一组的一个区间,原来该组的一个后缀。

对于第一个部分,前缀最值和不变。对于第二个部分,存在一个分界点,使得分界点前前缀最值取第一个部分的前缀最值,分界点后前缀最值取原区间中的前缀最值。第三个部分同理,接下来重点考虑第二个部分。

分界点可以二分找到。考虑如何计算分界点后的答案:因为不能让这个区间在原来组的前缀影响到答案,于是我们只能记录后缀答案:对于组 k,设 sumk(i) 表示只考虑 ii 之后的部分,整个后缀的前缀最值和。这可以通过从后往前地推来得到,若 i 后第一个比 i 大的位置为 j,那么 [i,j) 的前缀最值为 i 本身的值,后面的部分从 sumk(j) 转移即可。

要查询区间的前缀最值和可以通过类似的差分状物来求得,只不过要在边界处动一点手脚。

代码过于复杂,细节过于繁多,遂不实现。前两个自然段的内容非常有启发性,如果我们能够从 T 相同这一特点入手,或是想 m=1 时的情形,说不定就可以做出更多的部分。

屯题#

[BJOI2019] 删数#

[AH2017/HNOI2017] 影魔#

[JOISC2020] 首都#

今日不可做题#

[APIO2016] 烟火表演 + DS 部分剩余题目#

过于困难了。

LOJ6289 花朵 + ICPC2017 西安 D Islands (分治 FFT) (Source :树上问题)#

过于数学了。

[JOISC2020] 収穫 (Source: 树上问题)#

过于抽象了。

CF1326G Spiderweb Trees (Source: DP)#

过于 *3500 了。

作者:Meatherm

出处:https://www.cnblogs.com/Meatherm/p/17951168

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   Meatherm  阅读(80)  评论(2编辑  收藏  举报
相关博文:
阅读排行:
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
more_horiz
keyboard_arrow_up light_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示