Ducati做题题集

本文精选多道算法竞赛题目,涵盖主席树、欧拉反演、最短路径等高级算法,解析高效解题思路,包括优化算法的时间复杂度,如采用线段树优化建图过程等。

List

Easy Problem

CF840D\color {blue} {CF840D}CF840D
CF1422D\color {green} {CF1422D}CF1422D
P2048\color {blue} {P2048}P2048
P5283\color {purple} {P5283}P5283

Good Problem

P2303\color {blue} {P2303}P2303
P4768\color {blue} {P4768}P4768
CF1416D\color {blue} {CF1416D}CF1416D
P5025\color {purple} {P5025}P5025

Tips

CF804D

考虑主席树。

每次询问执行主席树上扫描即可。假设本题询问的区间是[L,R][L,R][L,R],当前扫到的值域区间[l,r][l,r][l,r]。如果左子区间的数在[L,R][L,R][L,R]中出现的次数之和超过了R−L+1k\frac {R-L+1} kkRL+1,那么搜索左区间,否则搜索右区间;如果都没超过直接返回。

考虑这么做的时间复杂度是多少。不难发现,对于主席树上的每一层至多只有kkk个位置被扫描到并继续向下进行搜索;所以总时间复杂度是O(nklog⁡n)O(nk \log n)O(nklogn)的。

P2303

通过欧拉反演,不难得到答案为sumd∣nφ(i)nisum_{d|n} \varphi(i) {\frac n i}sumdnφ(i)in

直接枚举因数是O(n)O(\sqrt n)O(n)的,但是单次计算欧拉函数的代价之和很高。并且这个式子不能使用杜教筛来优化。

参考一下根号分治的思想我们不难得到正解。我们通过线性筛求出所有不超过limlimlim的数的φ\varphiφ值。我们找到所有nnn的约数pip_ipi,如果pip_ipi的值不超过limlimlim直接调用,否则暴力在根号复杂度内计算φ\varphiφ

limlimlim取到n0.72n^{0.72}n0.72的时候可以通过。

CF1422D

考虑规划一条路径为“从一个闪现点到另一个闪现点”,不难发现两点(a,b)(c,d)(a,b)(c,d)(a,b)(c,d)之间的路径距离为min(∣a−c∣,∣b−d∣)min(|a-c|,|b-d|)min(ac,bd)

但是,如果我们枚举所有的点对并连边的话时间复杂度,并跑最短路的时间复杂度为O(n2)O(n^2)O(n2)。我们需要优化这个算法。

首先,我们对模型进行一步转化。两点(a,b)(c,d)(a,b)(c,d)(a,b)(c,d)之间我们可以连两条边,边权分别是∣a−c∣|a-c|ac∣b−d∣|b-d|bd,这样就将两点之间的边权式子给简化了。同时,我们发现,对于满足横坐标分别为x1,x2,x3x_1,x_2,x_3x1,x2,x31,2,31,2,31,2,3号点,111222的距离为x2−x1x_2-x_1x2x1222333的距离为x3−x2x_3-x_2x3x2,而111333的距离是111222的距离与222333的距离之和!所以,我们根本不用连接111333号点。扩展到多个点的情况,我们只需要将横坐标排序并将相邻两个点做连边操作,再对纵坐标排序并将相邻两个点做连边操作即可。

由于边数为2(n−1)2(n-1)2(n1),点数为nnn,所以跑最短路的复杂度是O(nlog⁡n)O(n \log n)O(nlogn),可以通过。

P4786

NOI 2018 D1T1竟然是个裸题,NOI 2020 D1T1也是

首先,可爱的Yazid一定是开车到达某个节点rtrtrt并下车之后步行到终点。

我们建出关于海拔的Kruskal重构树。注意建立的应该是最大生成树而非最小生成树。根据此时重构树的小根堆性质,此时两个点能够互相到达当且仅当它们的LCA的点权大于ppp

此时我们先考虑一个暴力做法: 在Kruskal重构树上枚举这个LCA,要求这个LCA的点权必须大于ppp;对于所有满足要求的LCA,与vvv异子树的叶节点的disdisdis值的最小值即是答案。这里一个节点的disdisdis表示它与111号节点的最短路径长度。

考虑如何优化这个暴力。首先,disdisdis要用Dijkstra预处理出来①^{①}。然后,我们处理出每一个子树内的所有叶节点的disdisdis的最小值。对于每次询问,我们直接倍增即可求出答案。

时间复杂度为O(T×nlog⁡n)O(T×n \log n)O(T×nlogn)

①: 这句话是有深意的。这道题是OI历史上的一个里程碑——SPFA的死去。特加这一个注释,悼念已经离我们而去的SPFA算法。事实上,SPFA并没有完全死去,在Johnsen全源最短路、差分约束、负环判定以及一些极其难卡掉(如一些神奇的费用流建图题)或者数据完全随机的题目中有复杂度为线性,有重要的作用。

CF1416D

考虑给每条边一个边权,表示它在什么时候被删去。特别的,对于那些一直没有被删去的边,令它的权值为q+1q+1q+1

然后我们正序扫描一遍所有询问。不难发现第iii次询问的答案为: 从uuu开始只经过权值不超过iii的边所能到达的节点的最大点权,并将这个点的点权赋为000

是不是很熟悉这个模型?我们可以建立出一个关于边权的Kruskal重构树。每次查询的符合要求的节点均在一个子树中,这个子树的根可以倍增找到;找到之后,我们需要查询最小值并动态修改,可以采用dfs序+线段树来维护。

时间复杂度O((n+m)log⁡n)O((n+m) \log n)O((n+m)logn),比LCT不知道可爱到哪里去了。

P5025

套路题。

首先考虑暴力做法。先O(n2)O(n^2)O(n2)建图,然后缩点并执行一次拓扑排序(dpdpdp)。

考虑如何优化。不难发现这个连边有一个性质: 每个节点连向的点都在一个区间中。所以可以采用线段树来优化这个建图。线段树优化的建图同样可以缩点,只不过每个点本身都有一个权值。

我们可以通过二分找到每次连边的区间[l,r][l,r][l,r]。时间复杂度为O(nlog⁡n)O(n \log n)O(nlogn)

P2048

大套路题,然而我不会。

prei=∑j=1iajpre_i=\sum_{j=1}^i a_jprei=j=1iaj,那么不难得到∑i=lrai=prer−prel−1\sum_{i=l}^r a_i=pre_r-pre_{l-1}i=lrai=prerprel1。于是我们将题目做一个转化——在序列中选kkk对差在lllrrr之间的数,可以多次重复选择同一个数,使得每一对数的后者的preprepre减去前者的preprepre之和最大。

我们维护许多五元组(l,x,y,pos,val)(l,x,y,pos,val)(l,x,y,pos,val),表示选取的第一个位置lll,第二个位置在[x,y][x,y][x,y]区间中;满足prepos−prelpre_{pos}-pre_{l}preposprel最大的位置为pospospos且这个最大值为valvalval

我们将这些五元组扔进一个大根堆中。每次我们取出堆顶,并将这个区间拆分为两个形如(l,x,pos−1,pos′,val′)(l,x,pos-1,pos',val')(l,x,pos1,pos,val)的子五元组,分别重新计算这两个子区间的pos′pos'posval′val'val

采用RMQ来快速计算即可,时间复杂度为O(nlog⁡n+klog⁡n)O(n \log n+k \log n)O(nlogn+klogn)

异或粽子这题只需要将RMQ求区间最大值改为可持久化Trie树上查询,时间复杂度为O(nlog⁡m+klog⁡nlog⁡m)O(n \log m+k \log n \log m)O(nlogm+klognlogm),其中mmm为所有aia_iai的最大值。

<think> 我们有一个列表 motorcycles,然后我们打印它。 然后我们定义了一个变量 too_expensive,其值为 'ducati'。 接着我们使用 remove 方法从列表中移除这个值,并再次打印列表。 最后我们打印一条消息,说明这个牌子的摩托车对我们来说太贵了。 注意:remove() 方法会修改原列表,并且只移除第一个匹配项。如果列表中有多个 'ducati',则只有第一个会被移除。 另外,在打印消息时,我们使用了 f-string 并且对 too_expensive 调用了 title() 方法,使其首字母大写。 现在,我将按照要求给出代码和解释。 </think> 以下是针对给定代码的详细解释和分析: ```python motorcycles = ['honda', 'yamaha', 'suzuki','ducati'] # 创建摩托车品牌列表 print(motorcycles) # 打印初始列表:['honda', 'yamaha', 'suzuki', 'ducati'] too_expensive = 'ducati' # 标识过贵的品牌 motorcycles.remove(too_expensive) # 从列表中移除指定元素 print(motorcycles) # 打印修改后的列表:['honda', 'yamaha', 'suzuki'] print(f'\nA {too_expensive.title()} is too expensive for me') # 输出格式化消息:A Ducati is too expensive for me ``` ### 关键点解析: 1. **`remove()`方法特性**: - 直接修改原始列表(原地操作) - 移除列表中第一个匹配的元素值 - 如果元素不存在会抛出`ValueError` - 时间复杂度为 O(n),需要遍历列表查找元素 2. **字符串格式化**: - 使用 f-string 格式化字符串(Python 3.6+) - `title()`方法将字符串转为首字母大写格式 - 输出消息中品牌名称正确大写:"Ducati" 3. **列表修改验证**: - 初始列表包含四个元素 - 移除操作后列表减少到三个元素 - 原始列表被永久修改(不同于`sorted()`) ### 列表操作的替代方案: ```python # 方法1:使用索引和del(知道索引位置时) del motorcycles[3] # 方法2:使用pop()移除最后一个元素 removed = motorcycles.pop() print(f"A {removed.title()} is too expensive for me") # 方法3:列表推导式创建新列表(保持原列表不变) affordable = [bike for bike in motorcycles if bike != 'ducati'] ``` ### 执行结果说明: ``` 初始列表: ['honda', 'yamaha', 'suzuki', 'ducati'] 修改后列表: ['honda', 'yamaha', 'suzuki'] 消息输出: A Ducati is too expensive for me ```
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值