「SOL」JOISC2021 解题报告
JOIS(egment-Tree)C
1. 前言
很早之前教练让我们做这套题,我以为这套题应该挺简单,用几天的空余时间就能刷完,结果预想的短周期刷题变成了长周期刷题……(好像是整个团队里最后一个刷完的??)
大多数题目(除了「保镖」和「特技飞行」,我不知道把特技飞行这种题放 Day1 是不是想搞选手心态 QwQ)还是能够独立地想出来,但是代码长度堪忧,看了一下好像每道题的代码都比其他人要长一些,不知道是不是实现细节的问题。话说过来虽然代码要长一些,但是运行效率好像要快一些耶,目前「IOI 热病」和「最差记者 4」都是 LOJ 的速度榜一 awa。
这一发是把线段树给练爽了,真就 JOI Segment-Tree Camp 呗。
2. Day1 解析
2.1. IOI 热病
2.1.1. 评析
推导「每个人行走方向其实固定」这一点比较考察贪心构造的技巧。
而后面「李超线段树上查全局最小值,支持删除坐标」这个有一定套路(我自己的做法,可能比较复杂),但是主要还是考察代码实现能力。
2.1.2. 解析
先枚举第一个人往哪个方向走,然后对其他人的最优行走方向进行分析。
不妨设 ta 向上走,先考虑一些特殊的位置——考虑到能一个人与一个人相遇的人是米字格形状的,所以先分析以起点为中心的米字格形状。
- 显然 1 区域只能向下,5 区域只能向上。
- 分析 2 区域,首先不可能向右向上;若向下,有一个非常巧妙的分析是「在时刻 \(i\) 出现感染的范围是距离起点横纵坐标之差不超过 \(i\) 的位置」,手动模拟一下「可能出现感染的范围」和 2 区域的人行走的情况,发现不可能被感染;于是只能向左。同理 8 区域只能向右。
- 分析 3 区域,不可能向右向下;若向上,同样模拟「可能出现感染的范围」和 3 区域行走的情况,不可能感染,所以只能向左。同理 7 区域只能向右。
- 分析 4 区域,不可能向右向下;若向左,同样模拟「可能出现感染的范围」和 4 区域行走的情况,发现只能向上。同理 6 区域只能向上。
分析得到这些区域只可能向一个方向走,于是我们大胆猜测其他区域也是这样。实际上利用「可能出现感染的范围」的分析同样可以得到以下结论:
于是可以直接枚举第一个人走的方向,然后固定其他人走的方向。
接下来其实就是模拟感染的过程了。两个人相遇时可能会发生感染,但是必须要有一个人已经感染。记 \(d_i\) 表示第 \(i\) 个人被感染的时刻。则在 \(\ge d_i\) 的时刻,第 \(i\) 个人才具备感染能力。发现 \(d_i\) 其实就是最短路。
考虑用 Dijkstra 求最短路,设当前点为 \(u=(x_u,y_u)\)。不妨设 \(u\) 的行走方向为 \(y\) 正方向(即「向上」),\(u\) 能够更新到的点只有:
- 左上右上的对角线上的人,若这个人在 \(v=(x',y')\),则相遇时间为 \(|x'-x_u|\),当 \(|x'-x_u|\ge d_u\) 时会感染,更新 \(|x'-x_u|\overset \min\to d_v\);
- \(u\) 行走方向上的人 \(v=(x_u,y')\),则相遇时间为 \(\frac {y'-y_u}2\),当 \(\frac {y'-y_u}2\ge d_u\) 时会感染,更新 \(\frac {y'-y_u}2\overset \min\to d_v\);
我们发现每次更新,都是将对角线上或同行同列上,\(x\) 坐标(或 \(y\) 坐标)在某个范围内的点的 \(d\) 更新为一个关于 \(x\) 或 \(y\) 的一次式。于是可以对每条对角线、每行每列维护一棵李超线段树,每次区间插入一条直线,支持查找全局最小点。一个区间的最小点一定在端点取到,直接更新即可。
这样更新是支持了,但是 Dijkstra 还需要「弹出当前点」,也就是将当前这个 \(d\) 最小的点删除。李超线段树不是不支持删除吗?李超线段树不支持删除已经插入的直线,但是可以删除要考虑的点。我们可以给已经删除的点打标记,更新区间最小值时找到左右两端的未被删除的点,这种「只有删除,查找下一个未删除的点」的操作实际上是一个并查集的套路。
可能就是并查集运行效率非常高,使我的代码运行速度比其他人要快吧。
时间复杂度瓶颈在于李超线段树区间插入线段,\(\mathcal O(n\log^2n)\),常数较大。
2.1.3. 源代码
考虑到直接粘贴代码会使文章长度爆炸,这里直接附上 LOJ 的提交记录。
> Link LOJ - IOI热病 参考代码
2.2. 饮食区
2.2.1. 评析
部分分算法特别多,比较考验选手对各种算法的熟悉程度。
一开始我想的是分块,然后空间卡不过去……
2.2.2. 解析
删除操作非常麻烦。如果没有删除操作,那么我们可以整体二分,对每个查询找到第一次队列的人数大于等于 \(B_i\) 的时候。
考虑删除会产生什么影响。我们尝试「不真的进行删除操作」,而是把查询给定的位置 \(B_i\) 向后移动离开的人数。注意到队列可能小于 \(K_i\),不能直接向后移动 \(K_i\)。
用线段树分别维护「不考虑删除操作,当前队列有多少人」,以及「考虑删除操作,当前队列有多少人」。第一种就是区间加、单点查;第二种稍麻烦,每次有删除操作时,需要全部元素对 \(0\) 取 \(\max\),但是只涉及单点查询,可以直接维护形如 \(x'=\max\{x+a,b\}\) 的懒标记 \((a,b)\),而不用 Segment Tree Beats! 。
两个值的差就是询问前离开的人数,然后就可以整体二分了。注意需要提前判断是否无解。
时间复杂度 \(\mathcal O(n\log^2n)\)。
2.2.3. 源代码
> Link LOJ - 饮食区 参考代码
3. Day2 / Day3 解析
因为通信题暂时不做,然后「保镖」这道题又完全不会,Day2Day3 就合起来了……
3.1. 道路建设
3.1.1. 评析
这道题硬上复杂数据结构(树套树、KD树)也能做,但是在考场上比较消耗时间。
如果多花些时间思考有没有简单一些的方法,反而可能节省时间。毕竟出题人只要不是想要出防 AK 题会考虑代码的复杂程度。
3.1.2. 解析
显然是要用数据结构直接维护当前花费最小的方案,支持把最小的方案删除。
还是比较常见的套路,对每个点求出从它出发的最优方案,全部塞进堆里。每次从堆里取出全局最优方案,将其删除后找到对应点的次优方案。
曼哈顿距离有两个绝对值,如果硬上数据结构就需要树套树维护四个象限的点。但是题目规定起点终点交换本质相同,我们可以直接把点按 \((x,y)\) 双关键字排序,只考虑从较大的点出发到较小点的路径。这样一来 \(x\) 的绝对值就没了。
现在的问题就是维护 \((x,y)\lt(x_u,y_u)\) 的所有点中,\(y\) 在某个区间上的点的 \(\max\{-x-y\}\) 以及 \(\max\{-x+y\}\)。需要支持删除指定的点。
不管删除操作,那就是可持久化线段树。加上删除操作呢?那还是可持久化线段树,只在当前线段树上删除指定节点,把对应位置赋值为 \(-\infty\) 即可。
时空复杂度为 \(\mathcal O((n+K)\log n)\)。
3.1.3. 源代码
> Link LOJ - 道路建设 参考代码
3.2. 聚会 2
3.2.1. 评析
个人认为是一道比较一般的题目……仅仅是考察基础的树上信息维护的方法而已。
3.2.2. 解析
设出席者的点集为 \(S\),通过调整法(如果「开会地址」不符合下述条件,可以通过把它向某个方向调整,使代价不减,直到符合下述条件,即为代价最小的「开会地址」)可证明:
- 若 \(|S|\) 为偶数,则「开会地址」可以取某条路径上的所有点;
- 若 \(|S|\) 为奇数,唯一的「开会地址」是 \(S\) 中距离其他点距离之和最小的点。
所以我们只需要回答 \(|S|\) 为偶数的的情况。不妨枚举上述的「某条路径」为 \((u,v)\),
\(S\) 可取的点集为 \(\{u,v\}\cup S_1\cup S_2\),并且 \(S\) 在 \(\{u\}\cup S_1\) 中的点数必须和 \(\{v\}\cup S_2\) 中的点数相等,类似于中位数。于是 \(dist(u,v)\) 可以贡献到 \(|S|\le2(\min\{|S_1|,|S_2|\}+1)\) 的所有偶数 \(|S|\) 的答案。
考虑树形 DP,\(f(u,s)\) 表示 \(u\) 子树内,子树大小大于等于 \(s\) 的最深的点的深度。一边转移一边贡献到答案。\(s\) 不超过 \(u\) 的子树大小,可以考虑 Dsu on Tree 进行优化,时间复杂度 \(\mathcal O(n\log n)\)。
但是我们发现 Dsu on Tree 只能计算「折线型」的路径,而不能计算祖先到后继的路径。分别考虑较小值在后继方向和在祖先方向的情况:
- 后继的子树较小:在 DFS 时,用线段树维护子树大小大于后继子树的祖先的最小深度,或者利用单调性二分;
- 祖先的子树较小:直接线段树合并维护出当前子树内,子树大小在某个区间内的后继的最大深度。
复杂度仍然是 \(\mathcal O(n\log n)\)。
3.2.3. 源代码
> Link LOJ - 聚会 2 参考代码
4. Day4 解析
4.1. 活动参观 2
4.1.1. 评析
考察了非常经典的字典序的贪心性质以及选手选择算法的能力。
与道路建设一题相同,用较简单的算法往往可以节省大量时间。
4.1.2. 解析
先读对题,字典序是将参加的活动按编号排序后再比较,而不是按参加时间比较。
于是贪心地从小到大判断「是否可以参加第 \(i\) 个活动」。
假设要参加,首先 \(i\) 不能和已经参加的活动冲突。
然后 \(i\) 会把原来的一个空闲时间段 \([L,R]\) 分成两段 \([L,l_i],[r_i,R]\),记只考虑时间段 \([L,R]\),最多能够参加的活动数量为 \(f(L,R)\)。则原本 \([L,R]\) 提供 \(f(L,R)\) 的贡献,现在只能提供 \(f(L,l_i)+f(r_i,R)+1\) 的贡献。直接判断剩余次数是否足够即可。
怎么计算 \(f(L,R)\)?贪心地选取活动,每次选取可参加的右端点最小的活动,于是参加一个活动之后的下一个活动(的右端点)是唯一的,倍增即可。
4.1.3. 源代码
> Link LOJ - 活动参观 2 参考代码
4.2. 最差记者 4
4.2.1. 评析
一道不错的线段树合并优化 DP 的题,对这个技巧比较熟练的话应该能一眼看出来。
如果想到利用 DP 数组的单调性维护差分数组,可以在一定程度上简化代码,但是没想到这点也能做。
4.2.2. 解析
把选手的 rating 的 “≥” 关系建为有向图,\(u\to v\) 表示 \(u\) 的 rating 小于等于 \(v\) 的 rating。
每个点的入度为 \(1\),显然每个连通块是叶向基环树,不同连通块之间没有影响。基环树环上的点的 rating 必须相同,显然最终环的 rating 只可能是 \(1\) 或者环上某个点原本的 rating。
最后枚举环的取值,考虑对树的部分进行 DP。定义一个非常暴力的 DP 状态,\(f(u,w)\) 表示 \(u\) 的 rating 设为 \(w\),子树内的总花费的最小值。
\]
然而这样定义会使得 \(f(u,w)\) 有值的点很多,并且被分成前后缀两个部分。考虑到 \(c_i\) 之和为定值,我们可以反过来定义 \(f(u,w)\) 为 \(u\) 取 \(w\),\(u\) 子树内减免的花费的最大值。
\]
这样比较方便之后线段树合并。
转移式中有后缀 \(\max\),并且是子树求和合并的形式,可以用线段树合并。我们发现,后缀 \(\max\) 使得大多数位置都有值,如果在合并是直接新建节点下放懒标记,会导致线段树节点数爆炸,破坏线段树合并的时间复杂度。但是后缀 \(\max\) 改变的位置数量只有子树大小,称改变的位置为「关键点」,则只需要维护关键点的值即可。
比较显然的是 \(v_1,v_2\) 合并后的关键点就是 \(v_1,v_2\) 各自的关键点的并,于是在合并时维护两棵线段树各自的后缀 \(\max\) 即可。合并时若出现一棵线段树已经为空,会对另一棵线段树产生「整体加上后缀 \(\max\)」的贡献,这意味着要打加法标记。我们并不希望下放标记,还好加法标记比较简单,可以采用标记永久化。
整个代码最复杂的部分就是线段树合并的函数,具体可以参考代码。
4.2.3. 源代码
> Link LOJ - 最差记者 4 参考代码
THE END
Thanks for reading!
「SOL」JOISC2021 解题报告的更多相关文章
- 「ZJOI2016」旅行者 解题报告
「ZJOI2016」旅行者 对网格图进行分治. 每次从中间选一列,然后枚举每个这一列的格子作为起点跑最短路,进入子矩形时把询问划分一下,有点类似整体二分 至于复杂度么,我不会阿 Code: #incl ...
- 「HNOI2016」树 解题报告
「HNOI2016」树 事毒瘤题... 我一开始以为每次把大树的子树再接给大树,然后死活不知道咋做,心想怕不是个神仙题哦 然后看题解后才发现是把模板树的子树给大树,虽然思维上难度没啥了,但是还是很难写 ...
- 「HNOI2016」序列 解题报告
「HNOI2016」序列 有一些高妙的做法,懒得看 考虑莫队,考虑莫队咋移动区间 然后你在区间内部找一个最小值的位置,假设现在从右边加 最小值左边区间显然可以\(O(1)\),最小值右边的区间是断掉的 ...
- 「HNOI2016」网络 解题报告
「HNOI2016」网络 我有一个绝妙的可持久化树套树思路,可惜的是,它的空间是\(n\log^2 n\)的... 注意到对一个询问,我们可以二分答案 然后统计经过这个点大于当前答案的路径条数,如果这 ...
- 「HAOI2018」染色 解题报告
「HAOI2018」染色 是个套路题.. 考虑容斥 则恰好为\(k\)个颜色恰好为\(c\)次的贡献为 \[ \binom{m}{k}\sum_{i\ge k}(-1)^{i-k}\binom{m-k ...
- 「HNOI2016」最小公倍数 解题报告
「HNOI2016」最小公倍数 考虑暴力,对每个询问,处理出\(\le a,\le b\)的与询问点在一起的联通块,然后判断是否是一个联通块,且联通块\(a,b\)最大值是否满足要求. 然后很显然需要 ...
- 「SCOI2016」围棋 解题报告
「SCOI2016」围棋 打CF后困不拉基的,搞了一上午... 考虑直接状压棋子,然后发现会t 考虑我们需要上一行的状态本质上是某个位置为末尾是否可以匹配第一行的串 于是状态可以\(2^m\)压住了, ...
- 「SCOI2016」妖怪 解题报告
「SCOI2016」妖怪 玄妙...盲猜一个结论,然后过了,事后一证,然后假了,数据真水 首先要最小化 \[ \max_{i=1}^n (1+k)x_i+(1+\frac{1}{k})y_i \] \ ...
- 「SCOI2016」美味 解题报告
「SCOI2016」美味 状态极差无比,一个锤子题目而已 考虑每次对\(b\)和\(d\)求\(c=d \ xor \ (a+b)\)的最大值,因为异或每一位是独立的,所以我们可以尝试按位贪心. 如果 ...
- 「SCOI2016」萌萌哒 解题报告
「SCOI2016」萌萌哒 这思路厉害啊.. 容易发现有个暴力是并查集 然后我想了半天线段树优化无果 然后正解是倍增优化并查集 有这个思路就简单了,就是开一个并查集代表每个开头\(i\)每个长\(2^ ...
随机推荐
- 软工综合实践课设——员工招聘系统(参考BOSS直聘);Pyhton实现
应用背景: 随着科学技术的发展,岗位数量越来越多,特别是每逢毕业季找工作的人数也很多,如果人们找工作或者企业招人靠纯手工的话,费时费力,仅仅是筛选简历和费劲,并且员工找工作投简历可能得需要克服时间和空 ...
- Zabbix“专家坐诊”第180期问答汇总
问题一 Q:老师,请教个问题,zabbix通过自动发现扫描网段,然后添加主机,有没有什么办法区分路由器或者交换机类型的方法,这样才能把交换机模板或者路由器模板挂给对应的主机A:不多的话, 批量加2次模 ...
- 对线面试官:浅聊一下 Java 虚拟机栈?
对于 JVM(Java 虚拟机)来说,它有两个非常重要的区域,一个是栈(Java 虚拟机栈),另一个是堆.堆是 JVM 的存储单位,所有的对象和数组都是存储在此区域的:而栈是 JVM 的运行单位,它主 ...
- LeetCode_1. 两数之和
写在前面 难度:简单 原文链接:https://leetcode-cn.com/problems/two-sum/ 题目 给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和 ...
- QT 5 中文乱码,试试在PRO文件加入这几行代码
msvc{ QMAKE_CFLAGS += /utf-8 QMAKE_CXXFLAGS += /utf-8 }
- STM32F0_HAL初始化系列:FLASH写入
//读 read_temp = *(__IO uint32_t*)value_address; //写 static void flash_write(uint32_t address, uint32 ...
- 调用后台接口实现Excel导出功能以及导出乱码问题解决
实现效果 在导出表格数据的时候,通常分为两种情况 页面列表数据导出 接口返回数据导出 这里主要介绍接口返回数据导出,关于页面的列表数据导出,请看另一篇:vue3+element表格数据导出 接口返回数 ...
- Binary &Op是什么
前言 在并行开发时我们经常会用到Pstream::gather()函数或是全局函数reduce()或者其他,需要输入参数Binary &Op,本篇主要讨论Binary &Op是什么 t ...
- Android:Fragment 和 include 标签引入布局的区别
Fragment 存在于 Activity 中,但是 Fragment 管理自己的界面和逻辑,表面上看,Fragment 最终的布局还是要被压入到 Activity 中的布局中.03#Android ...
- CSS 数学函数与容器查询实现不定宽文本溢出跑马灯效果
在许久之前,曾经写过这样一篇文章 -- 不定宽溢出文本适配滚动.我们实现了这样一种效果: 文本内容不超过容器宽度,正常展示 文本内容超过容器的情况,内容可以进行跑马灯来回滚动展示 像是这样: 但是,之 ...