SDOI 2024 考前做题

1. P9126 [USACO23FEB] Moo Route II S

首先注意到不一定保证 risi,否则就是最短路裸题了。

注意到此时相当于负权图最短路。spfa 也许能过,但是我们想要复杂度正确的写法。

利用一下一条边出入时间固定(至少中途不会变)的性质:不难发现每条边最多只会走一次。不妨考虑 dfs,记录当前的位置和时间。考虑扩展,记当前的时间为 t,则一条变能走需要满足 rt。不难对每个点出边按 r 降序排序之后满足条件的是一段前缀。使用 vector 存图,每次记录一下当前弧,搜索即可。

注意当前弧要在 dfs 下去之后更新,不然如果出现环就寄了。可以参考代码。

时间复杂度 O(nlogn),瓶颈在排序。

Code

2. P10199 [湖北省选模拟 2024] 时与风 / wind

是上题的加强版。

首先判断一条路径是否合法:显然充要条件是前一条边的 [L,R] 被后一条边的 [O,C] 包含。然后发现随机是诈骗,只需要取 R 就是答案。

还是考虑 dfs,不同的是记录的时间变成了区间。可以注意到数据范围有 L,O20,因此可以考虑对于每个 L 将以其作为左端点的 R 降序排序并维护当前弧即可。

复杂度不变。

Code

3. P8289 [省选联考 2022] 预处理器

简单模拟。

首先 #define#undef 是好处理的,只需要写一个函数判断标识符即可。在这个过程中用 umap 记录每个宏名被换成什么。

然后考虑普通文本的展开。首先找到每一段极长标识符,然后由于可能递归展开,写一个 dfs 模拟这个过程。整个过程和刚才的极其相似,先将其替换,并再用一个 umap 记录下其正在被展开,然后再遍历所有极长段递归展开。最后结束的时候再将其从 umap 中删掉。其他非标识符直接拼接即可。

需要注意的一点是,umap 删除元素不能直接 =0(如果你判的是 .count()),需要 erase

于是没了,你注意到 n100 所以随便过。

Code

4. P10060 [SNOI2024] 树 V 图

妙妙题。

首先进行一点简单的判无解:关键点的 f 显然是其本身在关键点集合中的编号,所以如果 f 数组没有取遍 [1,k] 就是无解。

首先不难注意到一个事实:f 相同的点构成一个连通块。这是显然的,考虑反证。假设两个 f 相同的点中间有其他点,则中间的点一定还是离这个 f 更近。证毕。

由于是在树上,因此两个不同的连通块之间至多有一处相交,就是较深的连通块的顶部节点和其父亲。这个东西可以直接预处理。具体来说就是如果一条边两端颜色不同,就记录下交点。同时如果儿子的颜色之前访问过了,说明不是一个连通块,无解。注意这么做的话需要在一开始将根的颜色标记为已访问,不然会出问题。

然后我们通过简单的 dfs 求出树上节点两两之间的距离,复杂度 O(n2)

考虑两个连通块的交点,那么显然要满足:这两个点到各自关键点的距离相差不超过 1。至于到底是多一还是少一要根据关键点的编号判断。

于是可以考虑将连通块缩点,然后 dp:设 fu,i 表示颜色 u 的连通块选 i 作为关键点,并满足子树内限制的方案数。答案就是新树上树根的 f 之和。注意到只有 u 联通块内的点的 dp 值有意义,转移就考虑枚举 u 选的 x 和儿子 v 选的 y,那么 fu,x=vsonuf(y)=vfv,y。注意到每个 x 至多和 O(n) 个点一起被枚举,所以最终复杂度就是 O(n2)

Code

5. Comet OJ Contest #14 E

有点意思的题,可以加深对 tarjan 代码 的理解。

首先一个环是可以随便走的,因此启发我们对 SCC 缩点。

但是我们发现缩点之后边权成了点权。这是不好的,所以考虑转化一下。

注意到对于点数 >1 的 SCC,内部是存在边的。不难发现我们只需要取出边权的 max,min 即可。建立三个点 li,midi,ri,然后连边 limidi,边权为 max;连边 midiri,边权为 min。然后新图连边就直接 rilj 即可。此时对于点数 =1 的 SCC,需要令 li=midi=ri

一些细节:在重建图的时候,若 sccu=sccv,不难发现可以直接用 w 去更新所在 SCC 的边权 max,min,就不用在 tarjan 的过程中去记录了,并且有重边也不会影响;若 sccusccv,直接连边即可,不需要判重(这是我之前一直犯的一个错误,就是 tarjan 缩点后建图不需要判重,因为 topo sort 的时候一定会遍历每一条边,从而会将 v 入队),而且判重之后如果存在边权不同的重边也不好处理。

然后考虑 dp。设 fi,0/1/2 表示从起点走到 i 的最大值/最小值/答案。初始 fi,0=fi,2=,fi,1=+(注意这里点已经没有点权了,只有边权)。

考虑转移。若存在出边 uv,边权为 w,那么先令 mx=max(fu,0,w),mn=min(fu,1,w),那么有转移:

fv,0max(fv,0,mx)

fv,1min(fv,1,mn)

fv,2max(fv,2,fu,2,mxw,wmn)

这里有个小技巧:因为我们无法记录某条路径的 max/min,所以可以直接钦定当前点为 max/min,并更新答案。

无解:如果终点 =1/dp 值没有被更新/不在某个 SCC 里 则无解。为什么会存在不在 SCC 里的情况呢?因为我们是从 1 出发,所以 tarjan 的时候只遍历 1 所在的连通块。如果该点和 1 不在一个连通块则无解。

时间复杂度 O(n)

Code

官方题解

6. P2824 [HEOI2016/TJOI2016] 排序

首先考虑序列只有 0/1 怎么做。

不难发现可以求出 0/1 的数量 c0/c1,那么升序排序就相当于将前 c0 个位置覆盖为 0,后面的位置覆盖为 1。降序同理。

回归原问题,发现此时只需要求一个位置的值。考虑离线,二分答案,将 mid 的视作 0,大于的视作 1,然后直接做就行了。

Code

7. MX【二月份 -- CSP-S 全真模拟】-- T3 --围堵

傻逼结论 && 诈骗题。

注意到题目规定(看样例)一开始所在的 1 不属于那 m 个点,于是初始令 dep1=0,求出每个点的 dep。令 dep=m 的点为关键点。

对于每个点,如果子树内存在 2 个关键点,那么必须要封住这个点。所以如果这种点数量 m, 则无解(等于也不行,这样就能直接走过去了)。还有一种情况是子树内 >0 个关键点的情况,此时数量 >m 则无解(因为封的人是先手)。

于是没了,O(n)。出题人开 n,m400 纯司马。

Code

8. MX【二月份 -- CSP-S 全真模拟】-- T4 --畜牧

这才是好题。

首先有一个关键结论:一次只会卖一个点,而且全卖光。

考虑证明:只要我们可以把两个点的情况合并成一个点,就证完了。

考虑反证,假设在 i,j 处买了两次,i<j,在 i 处买了 x 个。不难发现此时总价格是 xpi+(r=1ikrx)pjt=i+1jkt。分成两种情况讨论:

  • pi>pjt=i+1jkt

简单推一下:

xpi+(r=1ikrx)pjt=i+1jkt

=xpixpjt=i+1jkt+r=1ikrpjt=i+1jkt

考虑作差:

pir=1ikr(xpixpjt=i+1jkt+r=1ikrpjt=i+1jkt)

=r=1ikr(pipjt=i+1jkt)x(pipjt=i+1jkt)

=(r=1ikrx)(pipjt=i+1jkt)

0

  • pipjt=i+1jkt

借用一下上面的结果:

xpixpjt=i+1jkt+r=1ikrpjt=i+1jkt

=xpixpjt=i+1jkt+r=1jkrpj

=r=1jkrpjx(pipjt=i+1jkt)

r=1jkrpj

得证。

接下来我们考虑 i 什么时候比 j 优:

pir=1ikr>pjr=1jkr

pi>pjr=i+1jkr

注意到 p 只有 109,所以直觉上 i,j 不会太远。

准确来说,对于一个 k1 的位置,我们将后面 k=1 的一段和其合并起来,形成若干连续段。这样每个段内的 k 都是一样的。那么 i,j 至多只隔着 logV 段。

显然越靠后 k 越大,所以我们只需要考虑最后面的 logV 段。注意到每一段我们都要选 p 的最大值,可以使用线段树维护。比较就采用上文的方式,用一个 __int128 存储过程中 k 的乘积即可。当然最后算答案的时候还是要乘上前面的前缀积的,这个可以树状数组维护。

实现过程中,可以用 set 存储每个段的左端点,并用 vector 存储取出来的段。

时间复杂度 O(nlog2n),大概?

Code

9. P9168 [省选联考 2023] 人员调度 48 分

O(nmlog2n) 的做法。

注意到我们可以从底向上考虑,这样子树内的点就是固定不可移动的了,然后考虑把当前节点的人向子树内调动。

具体来说,每个人维护三个 multiset,分别存储初始在这个节点上的人(的能力值),子树内已经分配好的人,子树内空闲对答案没有贡献的人。分别记为 st,ok,gg

注意到没有贡献的人显然是能力值最小的,因此可以这么设计算法:首先将所有儿子的 okgg merge 起来,然后考虑加入当前点的 st:从大到小加入,如果当前 ok 没有满就加入,否则考虑用当前数替换 ok 的最小值,并将被淘汰的放入 gg

注意到合并 multiset 的过程可以用启发式合并,每次直接 swap 当前点和重儿子的 multiset(相当于直接清空重儿子的 multiset),然后对于轻儿子和当前点的 st 再暴力插入。这样每个数只会被暴力插入 O(logn) 次,每次插入 O(logn),这样总复杂度就是 O(nmlog2n) 了。

复杂度稍微有点高,但是由于时限 5s 还是可以过的。

Code

UPD;突然发现不用维护 gg,破防了。

Code

又 UPD:不难发现可以直接使用堆代替 multiset。由于每次一个堆只需要查询最大/最小值,因此可以直接维护。删除就打下标记即可。

使用 pbds 的配对堆即可做到 O(nmlogn)

Code

posted @   Southern_Dynasty  阅读(99)  评论(2编辑  收藏  举报
相关博文:
阅读排行:
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
点击右上角即可分享
微信分享提示