Tasks
cdq 分治、扩欧拉。
Problems
概率、构造、Ad-hoc、数数。
一眼矩阵乘法。n,m 很大需要直接写快读,在快读里取模。但是矩阵的费马小定理比较奇怪。
又是线段树优化 dp。题面有点模糊不清。
设 dp(i) 表示 [1,i] 的答案,dp(i)=minj=i−2bi−2adp(j)+1。
对于每个奶牛的草区 [Si,Ei],[Si+1,Ei−1] 中的每个点都不能作为喷灌器覆盖区间的端点,标记这些点。我们可以总结出:[l1,r1] 包含 [l2,r2] 的充要条件为 l1<l2∧r2<r1,即 l1,r1∈/[l2+1,r2−1]。
依次枚举未被标记的点,线段树转移即可。
期望好题,题解很有误导性,写的都很 rubbish。
三次方的期望可以用经典套路降幂。
考虑设 f3(i) 表示 [1,i] 的答案(即连击次数的立方和的期望),f2(i) 表示 [1,i] 中后缀连击次数的平方的期望,f1(i) 表示 [1,i] 中后缀连击次数的期望。注意「后缀」,也就是说 f1,f2 所期望的连击是形如 [j,i](1≤j≤i≤n)。f3 则不同,下面的递推式可以看出区别。
f1(i)f2(i)f3(i)=(f1(i−1)+1)×pi+0×(1−pi)=(f2(i−1)+2f1(i−1)+1)×pi+0×(1−pi)=(f3(i−1)+3f2(i−1)+3f1(i−1)+1)×pi+f3(i−1)×(1−pi)
相比之下,f3 有一个类似前缀和的累加。这是绝大多数题解没有提到的,或者直接化简,好看又难以真正理解。还有一堆灰蓝绿一遍过,他 * 的。
答案就是 f3(n)。
约数和入门题。
显然要考虑求 ∑i=1nσ(i),即约数和的前缀和。直接求是困难的,反过来考虑每个数对倍数的贡献。
i 在 [1,n] 内有 ⌊in⌋ 个倍数,每个倍数贡献 i 的值,即 i 的总贡献为 ⌊in⌋×i。转化为求
i=1∑n⌊in⌋×i
显然可以用数论分块 O(V) 计算。
形式化的推式子:
i=1∑nσ(i)=i=1∑nj∣i∑j=j=1∑nj∣i∑j=j=1∑nj×i=1∑n[j∣i]=j=1∑nj×⌊jn⌋
三维偏序 dp。设 f1(i),g1(i) 分别表示 [1,i] 中选,强制选 i,最大的数量和对应方案数。f2,g2 为后缀,同理。
f1(i)=j<ihj≥hivj≥vimax{f1(j)}+1
用 cdq 分治解决三维偏序,在分治过程中更新 f,g。正反做两遍 cdq。
ans=max{f1(i)},sum=f1(i)=ans∑g1(i)Pi=[f1(i)+f2(i)−1=ans]×sumg1(i)×g2(i)
注意直接 dp 概率是不可行的,没有考虑后继路径的影响。
cdq 分治的顺序由题目决定,每道题的分治顺序可能不一样。同时可能需要添加额外的排序。
操作数 qt 最大是 107,所以只能 O(1) 处理每个操作。n 最大 109,所以不能保存每个数。操作只有单点、全局,考虑打标记。
考虑分成两类位置:平凡和非平凡。我们称一个位置是平凡的,当且仅当从上次全局赋值以来没有单点修改过。反之为非平凡的。不难发现,所有平凡位置上的数均相等,我们称其为平凡值。
我们使用 fill 表示上次全局赋的值(初始为 0),mul 为乘法标记,add 为加法标记。于是可以用 fill×mul+add 表示平凡值。这样就解决了平凡位置。
开一个哈希表(unordered_map)imp,存储非平凡位置上的值。一种常见的想法是:单点修改时直接令 imp(p)←v。但这样一来,如果后续有全局乘法或全局加法,无法快速更新 imp 中的值。
解决方案也很简单,修改时令 imp(p)←(v−add)×mul−1,这样就可以满足 imp(p)×mul+add=v。全局乘、加只需更新标记,访问非平凡位置上的值时,乘、加标记即可还原出真正的值。
为了全局求和,需要维护 sum,并在操作时更新。要特判全局乘以 0 的情况。记得预处理逆元。
以左端点排序,设 1…i 的答案为 fi,则第 i 条线段有两种可能:
- 不取,复杂度之和仍为 fi−1;
- 取,考虑哪些子集复杂度有变化(+1)。显然,这些子集必须满足其中没有任何一个元素(线段)与第 i 条线段相交。即这些子集必定是 所有不与第 i 条线段相交的线段(的集合)的子集。我们可以用前缀和求出右端点 ≤j 的线段数量 sj,那么那些子集的数量即为 2s(j)。每个子集贡献为 1,复杂度之和为 fi−1+2s(j)。
于是 fi=2fi−1+2s(j),递推计算即可。
可以发现对于每一行必然有:在这一行走到的最左边 (i,j),走到下一行的位置 (i,k),从上一行来到这一行的位置 (i,l),同时 j≤k≤l。
考虑设 dp(x,y) 表示现在在 (x,y) 的最优解,发现无法转移:因为不知道第一次来该行的位置,可能走出不合法的路径。于是设 dp(i,k) 表示现在在 (i,k) 准备走到下一行,此时的最优解。求出最小子段和 (i,j)∼(i,k)=∑o=jka(i,o),同时计算向后的最小子段和 (i,k)∼(i,l)+dp=dp(i+1,l)+∑o=kla(i,o)。相加即为答案。
只能考虑 dp。若设 dp(i,j) 表示从 (1,1) 走到 (i,j) 的最大和,可以发现无法保证不重复,无法转移。
考虑再细分状态。发现如果从 (i−1,j) 走到 (i,j),则不能往回走。也就是说,状态可以保存来时的方向,这样就能保证不重复。具体看代码。
可以发现流水的关系是一棵树,可以使用单调栈求出。问题即转化成跳多少次父亲,使得路径上水被消耗完。
暴力跳不行,考虑倍增。用倍增数组求出点 i 向上跳 2j 步跳到哪个点、途中消耗水量。询问时直接从大到小尝试 j 跳即可。细节小多。
很好想到线段树优化 ddp。区间加难以处理,考虑差分。
观察可以发现选的区间必然单调,否则可以拆成多个单调的区间,比原方案更优。
观察对应情况下差分数组上的情况。在原数组上最大化极差,在差分数组上对应类似最大子段和(差分值要取绝对值)。
1 2 3 4 5 6 7 8 9 10
| case 1 id | 1 2 3 4| 5 6 a | 1 1 4 5| 1 4 d | 0 3 1|-4 3 ~~~~~ ~ case 2 id | 1 2 3|4 5|6 a | 1 1 4|5 1|4 d | 0 3|1 -4|3 ~~~ ~~
|
可以发现分割后的位置上的差分值不能取。因为它们跨块了。其他位置表示的是单调区间相邻两项的极差,故整个区间的极差为这些位置上的差分值之和。
设 dp(l,r,i,j) 表示对于差分数组上的 [l,r],i 表示取不取左端点,j 表示取不取右端点,二者均为合并服务。
mid=2l+rdp(l,r,i,j)={dp(l,mid,i,1)+dp(mid+1,r,1,j)max(dp(l,mid,i,0)+dp(mid+1,r,1,j),dp(l,mid,i,1)+dp(mid+1,r,1,0))sgn(dmid)=sgn(dmid+1)⟺amid−1<amid<amid+1otherwise
按上式在线段树上合并两个区间即可。
是不是要好好学一下 ddp。
先忽略廊桥数量的限制。维护一个空闲的廊桥队列,每到达一架航班,就给它安排编号最小的廊桥供其使用,求出每个廊桥停靠的飞机数 cnti。
加上廊桥数量的限制,可以发现直接就解决了问题。枚举国内分配几个,求出国际有几个。对廊桥停靠的飞机数前缀和 si=∑j=1icntj,代表停靠廊桥编号小于等于 i 的飞机个数,也即分配 i 个廊桥时停在廊桥的飞机数。国内、国际相加取最大值即为答案。
分讨区间 dp。注意方案数可能算重:一个方案被多次统计,要分类以避免。
可以发现如果第一次取出 a 开头元素,则另一次出现该元素的位置一定是最后一个出队的,因为该元素需分别处于 b 的开头、结尾来实现回文。取出 a 结尾元素同理。
该元素另一个出现位置将 a 划分为两个栈,栈底分别为上述位置的两侧。
具体来说,若有 a1=ai,则两个栈(栈底到栈顶)分别为:
- ai−1∼a2;
- ai+1∼an。
若 an=aj 同理:
- aj−1∼a1;
- aj+1∼an−1。
问题转化为:每次从两个栈顶中选一个出栈,放入 b 末尾,使 b 回文。
类似的,注意到如果取出了某个元素 x,则另一个 x 必须最后一个出栈。即 x 必须同时存在于栈顶和栈底(具体在哪个栈、是否在同一个栈并不重要)。这样,如果删去 x 对最优解没有影响。
于是,可以将栈改为双端队列,只需寻找这样的 x,将它们两个同时出队,保存答案即可。如果找不到 x 则无解。如果有多个 x 取使字典序最小的。
第一个元素有两种取法,需分别求解。
对于每个方案,可以由 (l1,rn)…,(ln,rn) 表示。li,ri 分别表示第 i 层的左右端点。
自然的 dp:设 dp(i,l,r) 表示已摆了下面 i 层,第 i 层摆放为 (l,r) 的方案数。转移方程:
dp(i,l,r)=j=1∑lk=r∑mdp(i−1,j,k)(第 i 层的 [l,r] 没有 X)
边界
dp(0,1,m)=1
答案即枚举搭 i 层,累加即可。
ans=i=0∑nl=1∑mr=l∑mdp(i,l,r)
注意 i 从 0 开始,即包括啥都不搭的方案。
代码实现略有不同。
表达式求值,考虑栈模拟(其实没有栈)。
- 如果前面已经短路,
- 有左括号跳括号,跳完为止;
- 若为右括号,则短路结束;
- 如果为
0&
型短路且当前为 |
,即 0&...|...
,则有 0|...
,短路结束;
- 反之为
1|
型短路且当前为 &
,即 1|...&...
,则无关紧要;
- 若
0&
型短路且当前为 &
,即 0&...&...
,直接丝滑小连招继续短路;
- 若
1|
型同理。
- 反之前面没有短路,则前面都没有影响,另起炉灶,
- 若遇到数,则将值暂时置为该数;
- 若有短路,则设置为相应短路状态。
最小化 Wi−l×Li−r×Ri 即最大化 l×Li+r×Ri。当 Li≥Ri 时发现应让 l 尽量大,即放在最右边,反之亦然。故每次只可能停最左和最右边。故停第 i 辆时,有 maxl/r=n−i,直接计算即可。
答案为
ans=i=1∑nWi−(n−i)×max(Li,Ri)−0×min(Li,Ri)=i=1∑nWi−(n−i)×max(Li,Ri)
好题!
重要结论:[1,n] 中任意两个不同的正整数的 gcd 最大为 ⌊2n⌋。故 gcd 最多 ⌊2n⌋ 个取值,即为 k 的理论上界。
考虑如何构造达到此上界。由于结论中最极端的情况就是 gcd(⌊2n⌋,2⌊2n⌋),考虑对于任意 i 向 2i 连边,形成若干条链:
(1,2,4,…),(3,6,12,…),(5,10,20,…),…
这样就覆盖了所有可能的 gcd 值。如果 k 没有达到上界,则舍弃后面的一些数
(1,n,(n−1),…,(2k+1)),(2,4,8,…),(3,6,12,…),(5,10,20,…),…
这样可以取到 ∀1≤i≤k,gcd(i,2i)。满足条件。
显然存在 O(n2) 的暴力 dp 做法。
发现有 12% 的数据,保证 ai 单调递增,同时因为“不相邻”是一个相对弱的条件,考虑将 ai 排序(需保留原下标)。可以枚举左端点,求最靠左的右端点,使得区间中可以组成合法子序列。
容易发现右端点是单调的,即若 l 的右端点为 r,则 l+1 的右端点 r′≥r。
接下来考虑怎样快速求一段区间能组成的最长合法子序列长度。如果从值下手,此题类似于最大的独立集问题(即有若干互不相同的元素,有若干条件诸如某两个元素不可在同一集合内,求最大的集合大小。可以抽象成图。),很难下手。不妨换个角度:直接将当前区间内所有元素按原下标填入一个新序列中。新序列可能有位置空出,也有若干连通块。设各连通块长度为 Li,则最长合法子序列长度为
i∑⌈2Li⌉
例如:对于 a=[1,5,0,9,4,8],排序后 a′=[0,1,4,5,8,9]。
对于 a′ 上区间 [2,4](即 [1,4,5]),以原下标填入 b=[1,5, , ,4, ],肉眼可见有两个连通块。第一个连通块可以选出 ⌈2/2⌉=1 个数,第二个可以选出 ⌈1/2⌉=1 个数,相加即为 2。
再比如区间 [1,5]([0,1,4,5,8]),填入 b=[1,5,0, ,4,8],最大长度为 ⌈3/2⌉+⌈2/2⌉=2+1=3。
“填入序列”可以使用类似珂朵莉树(ODT)的方法,维护若干现有区间。移动左端点时即删除左端点,看情况缩小区间或分裂。移动右端点即添加新右端点,看情况扩大区间或合并即可。
线段树优化 dp 好题!
题目中的条件是 ci=cj(颜色相同)或 [li,ri]∪[lj,rj]=∅(不相交)。
可以先将线段按左端点排序,由于我们只关心颜色和相交情况,故可以设 dp(ri,ci) 表示满足「选的线段中右端点最大的线段是 i,其右端点为 ri 颜色为 ci」的权值和的最大值。
或是难以直接处理的,考虑拆成两种情况:必需满足颜色相同、必须满足不相交。
分别对应下面转移转移式中 max 的后两项:
dp(ri,ci)←max{dp(ri,ci),wi+j=1maxridp(j,ci),wi+kmaxj=1maxli−1dp(j,k)}(1)
但这样无法解决包含的情况,所以:
∀j>ri,dp(j,ci)←dp(j,ci)+wi(2)
注意,(2) 是在 (1) 之前就完成了的。
不难发现 dp 的状态数是比较少的,只有 O(m) 个,可以直接动态开点线段树维护。
(1) 中红色转移、(2) 中的转移都不困难,分别区间查最大值和区间加即可。但是 (1) 中蓝色转移需要两层 max,无法遍历所有颜色,需要记录新增哪些颜色,一一取 max。具体可以看代码。
问:为什么是按左端点排序?能按右端点排序吗?
答:因为左端点排序才能在可接受复杂度内完成蓝色转移,按右端点排序无法实现。
此题显然不是简单的最短路。去除“边出现时刻”的限制,我们假定现在所有边都存在。
可以发现,由于边权均为 1,最短路退化为 bfs。
同时由“k 的非负整数倍”这一表述(以及 k≤100 的小范围),注意到对于两个状态(路径),如果他们的长度(耗时)在模 k 意义下同余,则它们重复了。即只需保留耗时更短的状态。因为对于耗时更长的状态,其接下来可能的行动都可以“移植”到耗时更短的状态上,从而被替代。
但是对于两个耗时在模 k 意义下不同余的状态,若其中一个状态有解或无解,与另一状态无关。因为它们的余数不同,可行的路径或是否无解也不一定相同。
于是在 bfs 判重的 vis 数组中,需要新增一维属于模 k 同余值。如果队头状态处于终点 n,且耗时为 k 非负整数倍(即模 k 余 0),则到达目标状态。
这相当于将每个点拆成 k 个,分别表示余数。
接下来考虑“边出现时刻”的限制。
一个做法是二分开始时间,按条件 bfs,对每次的耗时取 min。这样做正确性比较难证。
另一种做法是二分结束时间倒推。
更好的做法:发现能在起点等 k 的非负整数倍相当于能在任意点等 k 的非负整数倍。转移时若时间未到,直接在原地等 k 的负整数倍时间直至可以通过。易证这样一定是最优的。注意到这样边权不全是 1,跑 Dijkstra 即可。
https://usaco.org/current/data/sol_prob1_silver_jan24.html
我们定义 B(k) 为第一只严格大于前 k 只牛分数的牛的下标。这很有用,因为 FJ 的记忆限制可以表述为 B(ai)=hi。为了简化公式,我们同时设 prek=1≤l≤kmaxcl,即前缀最大值。
对于 B,我们显然有
- prei=preB(i)−1;
- cB(i)>prei。
对于 ∀1≤i<j<B(i),显然有 B(i)=B(j),反之可以证明矛盾。故如果存在两个限制 i,j,满足 ai<aj<B(ai)∧B(ai)=B(aj),无解。综上所述,对于任意 i≤j<B(i)=hi,我们可以得出 B(j) 的值(为 hi)。对于其他 B(j),我们称它们未知。
这足以使我们得出一个贪心的算法:枚举 i 从 1 到 n,存储满足 B(1)…B(i) 约束条件 c1…cn 的最小值。
对于一个确定的 i,如果 B(i) 未知,则无需操作。反之则有两种情况;
- prei=preB(i)−1:可以发现这并没有对 ci 施加额外的约束,因此我们不必修改它。cB(i) 有一个相关的变化,我们将在下一个情况后讨论。
- 反之 prei<preB(i)−1,我们需要找到一个 j≤i 并将 cj 设为 preB(i)−1。但是,
- 首先 j 需要尽可能大,且 cj 不能被题目给定。因为我们希望答案的字典序尽可能小。
- 其次不应存在 k 使得 j≤k∧B(k)<B(i)。否则修改后 preB(i)−1=cj<cB(k)≤preB(i)−1,矛盾。
- 因此如果不存在满足上述二条件的 j,无解。
对于两种情况,我们都需要保证 cB(i)>prei。若 cB(i) 没有给出,则可设为 prei+1。反之若 cB(i) 已经给出,我们需要保证它至少为此值(即满足 cB(i)>prei)。若无法满足,无解。
这种算法之所以有效,是因为每增加一个约束条件,就只能产生一个等价或更大的词典序列。因此,通过在数组中尽可能靠后的位置进行修改,得到的序列就是词典最小序列。最后,只需遍历序列,确保所有值小于等于 C,以检查问题中的最后一个约束条件是否满足。
时间复杂度 O(n2):https://www.luogu.com.cn/record/164344021。考虑优化。
第一部分即填写 B 可以使用双指针优化。具体的,枚举 1≤i≤n,维护 j,如果需要,则令 j 从 i+1 移动 B(i)−1 填写。如果发现 j 需要倒退并填写不同的值,则无解。
第二部分即构造 c。可以发现如果满足了 B(i) 的条件,那么 B(i+1),…,B(B(i)−1) 都可以跳过。于是我们有了时间复杂度 O(n) 的算法:https://www.luogu.com.cn/record/165903355。
https://usaco.org/current/data/sol_prob1_silver_feb24.html
首先可以发现右上角的顶点只能使用负斜率牛射,右下角的顶点只能用正斜率牛,反之会穿过目标内部。也就是说,我们至少分别需要 n 值正、负斜率牛,否则无解。
剩下的 2n 头牛分给左侧顶点。可以对左侧所有点按纵坐标排序,纵坐标大的分配负斜率牛,纵坐标小的分配正斜率牛。可以证明这样一定最优。
发现正斜率牛决定下界,负斜率牛决定上界。将两种牛(上界、下界)分开考虑。以下界为例,二分下界,求出每个点与下界连线的斜率,即射这个点的牛斜率必须不大于该值,反之会超出下界。将所有限制排序,与牛排序后斜率一一对应,若无法满足则当前下界不可取。反之可取。
奇妙期望 + 构造题。
对于构造题,为减轻构造难度,一般构造 特殊的情况。
容易通过期望推出和,但是难以通过和推出每条边的边权。干脆设边权均为 w。当边权都为 1 时,求出此时期望 E1。那么我们有
Ew≡E1×w≡×E1−1(modP)
若 E1≡0(modP),则不存在乘法逆元,需要特殊处理,很不优美,这里不讲。
可以直接打表找规律。
当 a 为奇数时,有 b=1,c=2,d=a+2。两边同乘 lowbit(a) 也成立,所以解决偶数。
考虑容斥,拆完之后需要求对于任意 0≤j≤n,使得存在 j 个位置满足 ∣Pi−i∣=k 的方案数。
相当于 n×n 的网格上,对于所有点形如 (i,i+k),(i,i−k) 中选出 j 个点不在同行同列。
限制转化为类似这样(n=5,k=1),即 (i,i+k),(i,i−k) 不能同时选,(i,i+k),(i+2k,i−k) 也不能同时选。限制条件连成一条链,相邻节点不能同时选。染色后,类似于链森林版本的「没有上司的舞会」,我们可以求出选择的方案数。
直接在网格上 dp 是困难的,必须将每条链拆开考虑(因为每条链上的点互不干扰)。
神!分块 + 凸包。
首先对 d 做前缀和 s。
考虑如何查询。将 si 看做点 i 的纵坐标,mi 看做点 i 的横坐标,用一条斜率 −v 的直线 l 从第 3 象限往第 1 象限扫,扫到的第一个点即为(贡献了)答案。
为什么?l 的 y 轴截距为 si+miv,正好为答案;扫描时 y 轴截距不断增大,第一个扫到的答案就最小。
如何实现扫描?维护下凸包,扫描时二分凸包边的斜率,找到相切的那个点。
考虑修改。使用分块,每块维护一个凸包,单点修 dp←f 就是对 [p,n] 的 s 做区间加 f−dp。单点修 mp←f 就暴力重构 p 所在块的凸包。
求凸包使用 Graham 算法,单调栈维护边的斜率即可。
块长取 n,总复杂度差不多 O(nlogn+qnlogn),总之能过。
我家鸡子(Intel® Core™ i5-2400 CPU @ 3.10GHz)上跑了 10s 什么意思?!
可以发现题目要求 dis(x,y)+dis(x,z)>dis(y,z),而显然有 dis(x,y)+dis(x,z)≥dis(y,z),故统计不合法的情况(即相等,又即在同一条链上),用所有方案减去不合法的即可。\xk
破环为链,发现到达的点(可以看的视频)必然包括 1,即可以分成 [n−l+1,n],[1,r+1] 这两个前缀、后缀的并。其中 0≤l,r<n,r+1<n−l+1。
将按按钮分为两类:移动和播放。移动次数显然是 l+r+min(l,r),即先「走短的一边,回来,走长的一边」。
剩下次数设为 k=m−l−r−min(l,r),用于播放。将视频 i 视为 m 个物品,第 j 个物品代表播放第 j 次的收益。对于一个前 / 后缀,我们只关心前 k 大物品,故归并维护即可。注:std::merge()
好用!用 std::vector
存前 k 大及其前缀和。
对每个前 / 后缀对应的前 k 大物品做前缀和,因为降序排序后取前缀最优。
我们遍历每个 r,然后依次增加 l。设决策 p,q 分别表示在前 / 后缀中取 ⋅+1 个(为对应 std::vector
中下标)。增加 l 时,前缀越来越优,尽可能缩减 q 即可。但 q 更优时增加 q。最后更新答案。
容斥好题。