UOJ Logo zhoukangyang的博客

博客

UOJ Round #26 题解

2023-10-29 23:19:35 By zhoukangyang

石子合并

idea, data, solution from myee

萌萌出题人给大家带来了一道萌萌签到题!

n4m8

直接 O(nm) 暴力枚举所有石子放到各个石堆的情况,然后暴力 check 就好啦!

注意处理相同元素时不要计重。

n=2m20

做法同上,但是 check 部分的常数要写的好一点。

n100m300o=1

对于 o=1 的情况,我们容易发现所有初始石堆内的石子编号都是不降的!

我们不妨考虑 dp,从小到大依次加入石子。

每次加入一段相同的石子时,我们可以选择给已有的石堆往末尾分别加入若干,或者加入某些还没有出现过的石堆。

dp 时需要记录当前已经考虑到哪个石子,以及已经有多少个石堆已经非空了。

复杂度容易做到 O(n2m)

n100m300o=0

现在有下降的石子编号了,我们咋办?

炸弹熊

我们考虑一下归并的过程:如果第一个石堆里的某个石子 x 后面有一堆比当前石子小的石子,那么当合并时加入 x 后,x 后面一堆编号比 x 小的石子会跟着一起加入,而且仍然保持跟在 x 后面。

因此,我们可以把这些 x 后面比 x 小的石子“归附”到 x 上,容易发现合并过程是不变的!

对于最后合成的序列,当一个石子比前面的任意某个石子小的时候,其一定归附在前面的某个石子上,因此删除其不影响答案!

这样我们就转化成了石子编号不降的情况,直接采用 o=1 时的算法即可。

n1000m3000

同样我们只考虑 o=1 的情况。

之前我们的 dp 没有前途,考虑有没有什么比较优秀的做法。

我们观察到,之前的做法复杂度比较高是因为题目里这个「非空」的限制很棘手。

如果没有这个非空的限制,那我们怎么计数呢?

思考熊

我们假设有 cj 个石子编号为 j,当前有 n 个石堆,而石堆可以为空,那么答案 fn

fn=j=1m(cj+n1cj)

理由是显然的:只用对每种编号的石子考虑插板法即可。

然而答案要求每个石堆均非空,我们设这样的答案为 gn

我们尝试考察 fngn 的关系,容易发现

fn=k(nk)gk

理由是,我们可以枚举当前哪些石堆是非空的,而剩下的石堆都是空的。

那么很自然的考虑二项式反演(不会?可以去 vfk 的博客里学):

gn=k(1)nk(nk)fk

因此我们只用求出 f0fn,就可以解出 gn 辣!(你也可以直接使用组合意义容斥得到一样的结果)

直接按照第一个公式暴力处理就是 O(nm) 的了。

n2×105m5×105

我们发现问题的关键在于要快速算出 f0fn。这咋办?

我们考虑观察一下特殊性质。

特殊性质:ai20

这种情况下我们只用枚举 j20 的情况,更大的情况因为 c=0 而不用考虑。

特殊性质:cj5

我们考虑到对于同样的 c(c+n1c) 是相同的,我们不妨考虑统计有多少个数 j 对应的 cj=k,即记

tk=j=1m[cj=k]

那么我们就有

fn=k(k+n1k)tk

直接处理即可。

一般情况

实际上有多少 k 满足 tk>0 呢?

我们注意到

k>0ktk=jcj=m

因此实际上满足 tk>0k 不会多于 O(m) 个!

直接暴力计算所有 f 即为 O(nm) 的。

总复杂度 O(m+nm),可以通过本题。

鼓掌熊

快速幂的 log 去哪了

那肯定有同学要问了:快速幂部分的 log 怎么没有进入复杂度呢?

其实是因为,我们计算 f 的复杂度实际上为

O(ntk1logtk)

tk1(1+log2tk)=p0k[tk2p]=p0k[tk2p>0]

而我们显然有

kktk2pm2p

因此

p0k[tk2p]=O(p0m2p)=O(m)

从而计算 f 的复杂度仍然为 O(nm)

一个更优的做法

我们考虑到

fn+1/fn=1nmj=1m(n+cj)

因此问题的关键在于对 k=1,2,,n,计算出

j=1m(k+cj)

使用多项式多点求值算法或者多项式连续点值平移可以直接做到 O(mlog2m)

能不能再给力点啊?

我们注意到 0<k+cjn+m<Mod,其中 Mod=998244353,因此我们设在以原根为 g=3 意义下 j 的离散对数为 sj,满足 gsjj(modMod),那么我们就只用对 k=1,2,,n 求出

hkj=1msk+cj(modMod1)

很显然我们有

hkj=1msk+jtj(modMod1)

这就是一个差卷积的形式,使用 MTT 即可在 O(mlogm) 的时间内解决。

问题来到计算 s1n+m 上,我们使用线性筛的方法,只用计算质数处的 sp 值,然后直接套用 Pohlig–Hellman algorithm 即可。由于 998244353 的优秀性质,计算单个离散对数的复杂度可以近似认为是 O(logMod) 的。

总复杂度不会超过 O(mlogMod)

超人熊

街头庆典

idea, data from 1kri, solution from 1kri, zhoukangyang

分析

考虑找到一组割掉边数最少的最优解,去分析这种最优解的结构。在这组最优解中,每条割掉的边都是不能省去的。

那么对于每条被割掉的边,它的两个端点一定是所在连通块中某条直径的端点。否则如果不割掉这条边,直径长度和也不会变大。

进一步分析,对于一个连通块,找到它的中心(直径的中点,有可能在某条边中间),那么它外围的所有端点(除了原树中的叶子节点)与这个中心的距离一定相等。换言之,每个连通块都恰包含了到某个中心距离不超过一个定值的所有节点。

我们考虑以某个原树的直径端点为根,这时我们发现,对于最终的每个连通块,都有一条直径以其深度最浅的点为端点。

算法一

以某个直径端点为根。

考虑 dp,设 fu 表示 u 到父亲的边被割掉,u 子树内的最小代价。然后设 su,d 表示 u 子树内,割掉所有与 u 距离为 d 的边,代价之和。

我们记 gu,i,它的含义是 u 号点所在连通块的中心在 u 子树内,且到其所在连通块内深度最浅点的距离为 i,最小贡献。显然 gu,0 表示的就是 f

对于 g 数组,分三种情况转移:中心是 u,中心在 u 到某个儿子的边上,中心在 u 的某个儿子的子树内。

对于三种情况,都容易写出 g 的转移式。

以第三种情况为例,gu,i=minvgv,i+1+su,isv,i1。其含义是,因为连通块是满的,所以 u 子树中除 v 子树以外要割掉恰好某个深度的所有边。

直接转移,时间复杂度 O(n2),可以得到 40 分。

算法二

我们对树进行长链剖分,对于每条长链同时处理答案。

假设现在处理的是包括根的长链,对于其余长链都已计算得到答案。

考虑一种特殊的情况,这条长链上的点所在的所有连通块的中心都在长链上。不难发现,这个限制等价于每个连通块都有直径完全在长链上。

我们设 vall,r 表示长链上第 l 到第 r 个节点构成了一个连通块直径。那么 vall,r 大约形如 i=lrsi,min(il,ri)。其中 si,d 为长链上第 i 个节点,割掉长链外所有距离其为 d 的边的贡献。

改令 fl 表示长链上第 l 个节点的原 f 值。那么 fl 形如 minr=ltfr+vall,r 再加上一些只与 lr 有关的简单贡献(其中 t 为长链上的节点个数)。

我们从大往小处理每个 l,类似扫描线,动态更新每个 rvall,r+fr。考虑从 l 处理到 l1,在所有 vall,r 计算的过程中有哪些 i 的贡献会发生变化。

riilil>mxi 时,i 就不会再发生变化了(mxi 表示 i 下挂的短子树的最大深度)。

又由于每条长链下挂的短子树最大深度和是 O(n) 的,所以发现每个 i 的贡献发生变化的时刻一共只有 O(n) 个。

对于每次 i 的贡献发生变化,它恰会同时影响一段后缀的 rval 值。这是不难理解的,因为它只会影响 il<rir,且影响只会与 l 有关而与 r 无关。

长剖维护 s 的值,用线段树优化 dp,能做到 O(nlogn) 的复杂度。

然而这个算法是建立在只处理包括根的长链且这条长链上的点所在的所有连通块的中心都在长链上的假设上的,无法获得什么分数。

算法三

还是考虑只处理包括根的长链,而允许长链上的点所在的连通块的中心在短子树内(或下挂短子树的边中)。

再重新考虑算法一中的 g 数组。发现 g 的第二维范围不能超过 u 子树中的最大深度,那么就自然想到长链剖分。

而这里不能直接长链剖分,是因为当中心在长链上时,第一、二种转移需要数组对位取 min,而且位数是整个子树而非短子树的最大深度,长链剖分无法保证时间复杂度。

而对于只考虑连通块中心在每条长链下挂的短子树内(或连接其的边上)时,就可以规避掉影响时间复杂度的部分了。

发现长链剖分可以解决所有中心不在长链上的转移,而算法二中的扫描线可以解决所有中心在长链上的转移,这两部分拼在一起就得到了所有可能的转移!

那么我们直接同时施加两种方法,就可以得到所有正确的 f 值了!

这个算法只考虑了处理包括根的长链的情况,而没有考虑对于其它长链怎么计算 g 的值,因而也无法获得什么分数。

算法四

现在唯一的问题是要求出 g 值,并且我们发现,在计算过程中,只用到了链顶的 g 值。

我们假设长链上的节点是从 0 编号的,并在长链上新增 t 个虚点,由深至浅标号为 1,2,...,t

那么我们发现,g0,i (这里 g 的第一维同样用点在长链上的位置表示)的含义与 fi 的含义几乎相同!

唯一的区别是,i 所在连通块的直径中心不能在 0 以上。这等价于,要么中心在短子树上,要么直径下端点(r)不小于 i。前者和之前是完全一样的,后者只要改一改原来线段树查询的区间,而对于其它过程都是一样的!

这样,我们通过类似求 f 的过程就可以求出所有的 g0,i 了。

这时,我们的算法不建立在任何假设之上,可以以 O(nlogn) 的时间复杂度通过所有数据。

更进一步,发现线段树的部分是可以由并查集替代的,就能做到 O(nα(n)) 甚至是线性!

由于时间限制十分宽裕,只要复杂度正确的做法都能通过。如果不会写复杂的长剖,甚至可以全部用线段树和线段树合并替代。

铁轨回收

idea, data, solution from zhoukangyang

算法 1

我会观察数据范围!

发现有 Bn=0 的分,输出 1 即可。期望得分 10 分。

算法 2

我会暴力!

模拟题意,时间复杂度 Θ((n1)!),拼上算法 1 期望得分 20 分。

算法 3

从前到后 DP,记录一个长度为 Bn+1 的数组 cci 代表了后面的点中被加了 i 的有 ci 个(如果被加了超过 Bn 那就认为加了 Bn)。

期望得分 50 分。

算法 4

留给实现得不好的正解。

Melania 同学用厉害 DP 过掉了 Bn10,在这里向他献上真挚的膜拜!

期望得分 7080 分。

算法 5

在后面的描述中,我们记题面里的 AiAi,执行完 1i1 的操作后第 i 个仓库有的铁轨长度为 Ai

假设第 i 个仓库选择了第 j 个仓库放铁轨放置,我们就连一条 ij 的边。显然这些边形成了一颗以 n 为根的有根树。

我们考虑统计执行完 1i1 的操作后,Ai=j 的方案数:

  • 对于 j<Bi,我们只要统计 [Ai+tsonjAt=j] 的方案数即可。
  • 对于 j=Bi,我们用总方案数减去 j<Bi 的方案数。

这样,我们可以枚举答案 i,然后从后往前做 DP:

dppos,S,k 表示在第 pos 个位置之后的点中,需要被连的 A 之和 的集合为 S ,且有 k 个点可以任意接受边(对于一个选择了统计 “总方案数” 的点,他的子树是什么值不重要,因此他前面的任意点都可以向他连边)。

转移可以考虑以下几种情况:

  • fapos 为那 k 个点中的一个,此时 kk+1
  • 统计 Apos=Bpos,并选择”统计总方案数“:此时 kk+1,并选择 S 中的一个元素减去 Bpos
  • 统计 Apos=Bpos,并选择”减去 j<Bpos 的方案“,即做 1 的贡献。此时选择 S 中的一个元素减去 Bpos,然后在 S 中加入一个 t<BpostApos
  • 统计 Apos<Bpos 的贡献。此时选择 S 中的一个元素减去 Apos,然后在 S 中加入 AposApos

注意到 S 中的元素和是单调不升的,因此状态数只有分拆数级别!

我们再把 DP 倒过来做(或者说,转置原理),就可以用一遍 DP 求出答案了。

预处理一下状态之间的转移,时间复杂度上界为 O(n2π(Bn)Bn1.5),期望得分 100 分,其中 π(Bn)Bn 的分拆数。

评论

没想到 T1 不少人因为漏处理阶乘 FST 了,出题人前来谢罪。/ng
评论回复
题出的好,中间忘了,给出题人点赞!
T3不跑暴力直接跑 B<=4 能过前三个样例导致挂了10分也在意料之中吗(
T3 写了个 map<vector<int>,int> 和 dfs,B<=4 的包被卡成屎了 /tuu
评论回复
zhoukangyang:是不是复杂度里多带了几个 n,出题人写的 map<vector<int>,int> 只跑了 17ms (https://uoj.ac/submission/661715)
ZSH_ZSH:回复 @zhoukangyang:谔谔,我自裁(
题出的好,中间忘了,给出题人点赞!
题出的好,中间忘了,给出题人点赞!
题出的好,中间忘了,给出题人点赞!
题出的好,中间忘了,给出题人点赞! %%%
大周王朝,使民战栗/jk 怎么我主观上比暑假 UNRday2 还要难(
评论回复
tl_xujiayi:但是题目不错,点赞!
炸弹熊
提出的很好,只是适合延伸拓展或者赛前准备来做,为出题人点赞!!!
![鼓掌熊](http://img.uoj.ac/utility/bear-applaud.gif)
@zhoukangyang T3 的算法 5 的倒数第二种情况为啥是 t<pos 啊/yun
评论回复
zhoukangyang:修了

发表评论

可以用@mike来提到mike这个用户,mike会被高亮显示。如果你真的想打“@”这个字符,请用“@@”。