欢迎来爆踩我~|

AFewSuns

园龄:4年11个月粉丝:42关注:3

IOI2024 集训队互测 做题记录

回归 whk 了,一段时间内不会继续更了。

Day 0

没打。

ps:CTT 上做了一遍,三道题都是简单的,就不讲了。

Day 1

D1T1 优惠购物

先考虑每个物品的前 aimodc 部分,这部分是最优的,因为它不会影响物品获得的优惠券。

之后再考虑剩下的部分,每使用 [1,c] 张优惠券都会使得当前物品获得优惠券减一,所以最优情况是用掉前面的 c 张优惠券,使得当前优惠券减一。可以操作多次。

如果最后还有剩余,那么每个物品可以使用 [1,c1] 张优惠券,使得当前物品获得优惠券减一,且最多操作一次。

于是可以把使用优惠券的过程看成这三部分,且显然优先级为 1>2>3

考虑用数学语言刻画条件:设 xi 表示第 i 个物品用了 xi 张优惠券,si=m+j=1i(xj+aixic) 表示买完前 i 个物品后还剩多少张优惠券,那么只需要求出满足 i[1,n],0xibi,si1xi 的情况下,xi 的最大值。

  • 对于第一部分,直接从前往后尽量贪心选取即可,因为给后面取显然等价于现在取。

  • 然后考虑第二部分。如果要对第 i 个物品操作,那么 xi 会增加 c,且 jisj 会减少 c+1。所以可以从后往前贪心,因为操作后面的物品对 s 的影响更小,每个物品可以操作的最多数量即为 min(bixic,si1xic,minj>isj1xjc+1)

  • 最后考虑第三部分,显然优先级顺序是 c1,c2,,1。于是从大到小枚举每一个值 v=c1,,1,还是一样从后往前贪心选取,每次判断当前位置能否选。暴力时间复杂度 O(nc)

为了方便,设 ti=si1xi,那么第三部分中 i 合法当且仅当 vmin(bixi,ti,minj>itj1)。于是第三部分实际上是不断取出 min(bixi,ti,minj>itj1) 最大同时 i 最大的位置进行操作。

注意到 bixi 是定值,且 min(ti,minj>itj1) 在任意时刻都是随 i 递增的。于是就可以提前按 bixi 从大到小排序,每次取出最大值 u,如果 buxu>min(tu,minj>utj1),那么 u 的值是由后面那部分决定的,先放一边;否则 u 的值就是 buxu,准备操作它。不过在操作它之前,还需要检查能否先将前面放一边的数操作了,由于它们的值是由 min(ti,minj>itj1) 决定的,也就有单调性,所以每次取出这之中最大的数,检查它的值是否大于等于 buxu

用优先队列维护当前扫到了但没考虑的数,然后用线段树维护 t,支持区间加和区间求 min。时间复杂度 O(nlogn)

D1T2 树哈希

把容斥的部分分写了,现在有 77 分。

正解看情况更。

D1T3 网格图最大流计数

咕。

Day 2

这场赢麻了。

D2T1 序列

先讲讲我的做法。

首先看到序列的限制,可以想到插入 dp,并且从小到大加数会好做很多。

再分析一下这个限制在加数过程中的体现,相当于如果有 i<jai>aj,那么 ij 中间不能再插入比它们大的数,换句话说 [i,j] 已经被并成一段了。

于是设 fi,j,k 表示目前已经加入了 i 数,加入了 j 数,并且序列恰好形成 k 段的方案数。每次转移新插入一种数,枚举插了多少个,插在了哪里,随便转移一下可以做到 O(n5)

答案相当于求 i=1nk=1n(mi)fi,n,k,预处理一下可以做到 O(qn)

考虑优化转移。转移相当于在段之间插入若干个数,然后第一个插入的数之后被并成一段;之后在末尾插入若干个数各自成一段。两部分可以分开转移:第一部分可以钦定从后往前插入一个个转移;第二部分随便转移。时间复杂度 O(n4)


题解做法不太一样。

对于满足“避免 120 模式”的序列,找出其所有严格前缀最大值 ai1,,aik,记 a0=i0=0,ik+1=n+1,那么对于任意 j[1,k][ij,ij+1) 内的数的值域在 [aij1,aij] 内,即分成若干段,且每一段都是“避免 120 模式”的。于是可以设 fn,m 表示长为 n,值域为 [0,m] 的序列个数,每次枚举第一段的长度和第一段第一个数的值,可以得到转移:

fn,m=i=1nj=0mfi1,jfni,mj

时间复杂度 O(n2m2+q)

实际上 fn,m 是关于 mn 次多项式,于是拉格朗日插值,时间复杂度 O(n4+qn)

D2T2 没有创意的题目名称

f 的限制列出来:

  • i[0,n],0filimi

  • i+j[0,n],fi+fjn,ffi+fj=fi+j

然后就不会了。。。

打个表先,发现几乎所有序列都有循环节,并且混循环的一定满足前面一部分 fi=i

其实很容易理解,因为 fi=i 就是一个合法序列。设 len 为循环节长度,x 为循环起始位置,那么猜测 ix,fii(modlen)。易证这是合法的。

感觉一下只有这种情况,那么现在唯一要满足的条件就是 i+j[0,n],fi+fjn

如果 x+len1n2,那么循环节内任意两个元素都在条件范围内,只需要保证循环节内的元素都有 fin2 即可。

否则由于第一个循环节及前面部分跨过了序列中点,那么考虑 fi=i 这个序列,它是合法的,并且所有的 fi+fni 已经顶到了上界 n。由于 fii(modlen),所以如果要让任意一个 filen,那么与之对应的那个数与它加起来必定 >n,矛盾。于是对于第一个循环节内的元素有 fi=i,之后的循环节与前面相同。

所以枚举 xlen,随便优化一下判定可以做到 O(n2)fi=i 的序列要避免算重。

之后考虑循环节从 0 开始。不难发现判定条件和前面基本相同,只不过多了一种情况:若 len 为偶数,那么 f0 可以为 len2(模 len 意义下)。

但是这种情况下 len1>n2 的时候不太好直接判定了。注意到循环节从 0 开始的时候只需要枚举一层 len,所以有多余的时间来 O(n) 判这个序列是否合法。

时间复杂度 O(n2)

D2T3 傅里叶与交通规划

咕。

Day 3

D3T1 Permutation Counting 2

pi<pi+1 的位置 i上升位pi1<pi+11 的值 i上升值。那么如果恰好有 x 个上升位,就相当于 p 排列有恰好 nx 个极长上升连续段。

对上升连续段进行容斥,即计算至少有 x 个上升连续段的方案。每次按值从小到大插入 dp,设 fi,j,k,l 表示现在考虑了 1i 的数,总共有 j 个上升连续段,并且 i 恰好在其中第 k 段,上升值个数为 l 的方案数。转移即枚举 i+1 在哪个段里,要么插在某个已知段的末尾,要么新开一段,根据它与 k 的大小关系来确定有没有新增上升值。直接做复杂度 O(n5),前缀和优化可以做到 O(n4),都能过 n100

无法继续优化,考虑对上升值同样容斥,即计算 p 至少有 x 个上升段,p1 至少有 y 个上升段的方案数。枚举 xy,相当于要把 1n 的数分成 y上升值段,每段里的数需要放进 x上升位段里,满足小的数放进的段在大的数放进的段的前面。设 ai,j 表示第 i上升值段放了多少个数在第 j上升位段里,其中 i[1,y],j[1,x],那么只需要保证 i,jai,j=n 且每一行每一列和不为 0

这是很好计算的,对后面每一行每一列和不为 0 的限制容斥一下即可。时间复杂度 O(n3)

D3T2 化学实验

看到询问容易想到建立 kruskal 重构树,而强制在线要求我们动态维护 kruskal 重构树。魔改一下,每次合并两点时不建立新点,而是直接连接两点,这样看起来会好维护很多。

考虑连边 x,y 会使重构树发生什么改变。不妨设 x<y,如果 yx 的祖先那么什么都不用变;否则 x,y 会在 y 的时候就连通,而不是等到 lca 的时候才连通。

拿出 xlcaylca 这两条链,手玩一下不难发现新的重构树相当于把这两条链并起来。于是用 LCT 维护,合并两条链相当于 splay 的可交并,时间复杂度 O(nlog2n)

D3T3 物理实验

咕。

Day 4

D4T1 数据库

相当于给长为 q 的序列 m 染色,如果存在某个位置与上一个和它同色的位置值相同,那么就不用花这个值的代价。

同时观察到如果它与上一个同色位置之间还有位置与它们值相同,那么不如将中间的这些位置也染成这种颜色,肯定更优。

prex 表示上一个与 x 值相同的位置,于是问题变为选一些位置 x,要求 prexx 同色,且中间不能再出现这种颜色,其价值为 wax。将所有 (prex,x) 中间的数 +1,那么选取集合合法当且仅当所有数 m1,要求在合法的前提下总价值最大。

考虑网络流,将 1q 的每个点看成边,每条边流量为 +,费用为 0。给 (prex,x) 加一看作连了一条从 prex+1 起点到 x1 终点的边,流量为 1,费用为 wax。而每个位置 m1 的限制可以看作从 STm1 的流量,求最大费用流。

直接做时间复杂度 O(q2m),可过。也可以用 dijkstra 优化到 O(qmlogq)

D4T2 左蓝右红

观察一下合法的染色长什么样。把被奇数个同色封闭曲线包含的区域染色,那么其合法当且仅当所有异色区域除端点外不交。

同时观察样例解释,发现若一块区域被偶数个矩形覆盖,则其无色;若两块区域恰好共一个顶点,那么它们异色。

考虑证明。由于题目保证任意两个矩形边界不共线,所以对于每一个矩形交点,它一定有四条出边,且上边与下边异色,左边与右边异色,于是就有对角一定异色。同时保证了对角异色也可反推出同一个矩形相邻两段异色。

而对于边相邻的两块区域,一定是一边恰好为这条边的颜色,一边无色。而最外围没有被矩形覆盖的区域无色,于是就有被偶数个矩形覆盖的区域无色。

然后就可以暴力了。把所有区域求出来,有若干个区域之间异色的限制,用并查集维护同异色关系即可,字典序最小和方案数都很好做。时间复杂度 O(n2)

考虑对 x 这一维扫描线,那么对于每个时刻,y 轴都有偶数条横线,其中奇数条与偶数条之间有颜色。每次新加一个矩形的左边界时,会把一部分有颜色的区域“分裂”成若干与原来异色的区域,其中新的有色区域与原来的有色区域成交错关系。可以看图理解一下。

这要求与左边界有交的那些有色区域同色,且与这些新的区域异色。于是用 set 维护当前所有同色连续段(即 ODT),每次相当于在 set 上遍历并删除一段区间,要求它们同色;之后再新加一段区间,要求这个区间与原来的那些区间异色。

扫到矩形右边界的时候同理,只不过两端需要特殊处理。使用并查集即可,时间复杂度 O(nlogn)

D4T3 世界沉睡童话

咕。

Day 5

D5T1 Xor Master

考虑简化 g(x,S) 求解过程。如果 S 是给定的,那么做一遍线性基+消元,然后对于所有二进制位,如果 x 这一位为 0 且线性基这一位有主元,那就异或上这个主元。最后的到的数就是答案。

然后考虑没有 1 操作且 3 操作 l=1,此时 a 是固定的,可以做一遍前缀和后转为求 i=lrg(sl1si,S),还是不太好求。

这启发我们去求解 g(xv,S) 这个子问题。如果已经求出 g(x,S),那么 g(xv,S) 一定是在 g(x,S)v 的基础上再异或一些线性基元素。于是不妨先把 v 用线性基内的元素消成最小值 v,然后 g(xv,S) 就等于 g(x,S)v

之后再考虑 2 操作带来的影响,即求解 g(x,S{v}),其中 v 是新的一个主元。如果已经求出了 g(x,S),那么考虑 v 在线性基代表的二进制位:若 g(x,S) 这一位为 0,则需要异或 v;否则什么都不用干。不过注意到异或完 v 后可能会影响后面的位从 1 变回 0,于是先用在 v 后面的线性基元素去消 v,之后再异或就没问题了。

对于询问,先考虑暴力。枚举 i:lr,假设现在已经求出了 g(j=li1aj,S),那么 g((j=li1aj)ai,S) 就可以通过前面所讲述的做法求出来,只需要维护一个 ai 表示 ai 通过线性基消成的最小值即可。于是设 A=g(0,S),那么 g((j=lraj),S)=Aalar,比之前的询问好看多了。

如何维护 a 呢?对于操作 1,可以类比之前的做法,把 v 消成最小值 v 之后让 ai 异或它就行了。对于操作 2,还是可以类比之前的做法,新加一个主元 v 后用它后面的元素消它,之后再让它更新一遍所有 ai。由于线性基最多更新 logV 次,所以复杂度没有问题。

现在的问题在于如何快速处理询问。设 a 的前缀异或和为 sA=g(0,S)sl1,那么现在需要询问 i=lrAsi,同时单点修改变成后缀异或。操作 2 由于涉及到所有数且只有 logV 次,所以直接重构。拆位后用线段树可以做到 O(nlog2V+qlognlogV)

复杂度瓶颈在于每次需要 O(nlogV) 时间建线段树。考虑用类似 UOJ671 的优化方法:每个线段树节点都需要维护 64 个值域在 [0,len] 范围内的数,表示当前区间内有多少个数在这一位为 1len 即为区间长度)。写出这 64 个数的二进制形式,那么每个数都会有 loglen 位。将其看成一个 64×loglen01 矩阵,那么原本是维护每一行的数,现在考虑维护每一列的数,即维护 loglen64 位二进制数。

这时所有线段树节点需要维护的信息大小总和就是 O(n) 的了,每次 pushup 模拟二进制加法,复杂度 O(logn)。于是建树的复杂度就降到 O(n) 的了,总时间复杂度 O(nlogV+qlognlogV)

D5T2 栞

普及题。我受不了了,整场卡在数据出锅的第一题,这题想都没想。

直接考虑给定 p,如何求字典序最小的 q

首先第一段肯定在 [1,nk+1] 之间,因为要使得字典序尽量少,所以肯定是先全选,然后一个一个从后面 pop,直到前面的字典序发生变化。换句话说,选取一段前缀使得这段前缀严格比后面小,且选的是最靠前的前缀。

同样地考虑第 2k 段,唯一要注意的是第 k 段必须全选。然后就可以枚举全排列求解了。

考虑直接求解。将划分的 k 段在 q 上表示出来,那么每一段都必须是递增的。然后考虑第一个 i 使得第 i 段的结尾是 nk+i,那么不难发现它后面必然是一个数一段,而它前面则只需要保证前面的段严格比后面的小,且每一段无法再分解成更小的前缀。而第 i1 段与第 i 段的限制也很好刻画,如果 i1 严格比 i 小,那么随便排(同样要满足不能分解为更小前缀);否则第 i 段最多只有第一个数比第 i1 小,且这个数一定放在最末尾,前面随便排。

枚举即可。时间复杂度 O(n3)

D5T3 数据结构

毛毛虫剖分一下,做完了。

学毛毛虫剖分的时候有想过这种扩展,但没想到真的有人会出出来这种大垃圾题。

类似 k=1 的,我们把一条重链一层层从上到下标号,已经标过号的就不标了。具体而言,先将这条重链从上到下标号;再把距离这条重链为 1 的点从上到下标号;以此类推,最后把距离这条重链为 3 的点从上到下标号。

那么这样标号的话距离某条重链 3 的点看起来就很连续了,除了最顶上若干个特殊的,在之前就被标过号的。于是就可以用线段树维护了。

讲讲怎么实现。对每个点 x,维护集合 S1x,k,表示 x 子树内与 x 距离恰好为 k 的点,其中 k[0,3];然后 S1x,4 表示 x 子树内与 x 距离 4 的点。不难发现每个集合会形成不超过 4 个连续区间。

同理维护集合 S2x,k,表示 x 轻子树内与 x 距离恰好为 k 的点(k=4 表示距离 4);维护集合 S3x,k 表示从 x 往上到重链链头 topx,在 topx 子树内且与这一条链距离恰好为 k 的点(k[0,3])。不难发现这些集合也会形成不超过 4 个连续区间。

递推预处理这三类集合是简单的;之后链操作和子树操作就可以很方便的通过访问这三类集合进行线段树操作了。时间复杂度 O(mk2log2n)

Day 6

这场输麻了。

D6T1 Grievous Lady

突然发现自己还没有学过爬山算法与模拟退火(爬山算法略有耳闻吧),你怎么这么菜啊。

这题一看就很随机化(?),由于最终结果选 a 的个数和选 b 的个数不会差很多,所以可以一开始的时候枚举 S 集合大小 cnt[n240,n2+40] 之间。然后随一个排列,将前 cnt 个选 a,后 ncnt 个选 b,之后随机选数翻转(指选 a 还是选 b 的状态翻转),如果更优就翻。卡卡时就过了。

也可以按照 aibi 从大到小排序,每次枚举前 i 个选 ai,后 ni 个选 bi,断点附近 10 个扰动一下就过了。

看起来稍微对一点的随机化都能过,但我写了六个随机化做法都过不了样例,怎么回事呢?


题解是非随机化做法。

咕。

D6T2 Axium Crisis

先考虑如何做 w{0,1},c=0

拿出树上的每一条有向路径,将它们按照字典序排序,那么原问题变成从中选出若干条边不相交的路径,它们的权值为长度之和减去相邻两项 lcp,求权值最大值。

直接从前往后 dp,设 fi,S 表示只考虑前 i 条路径,选了第 i 条路径,且当前选取的路径边集并为 S 的最大权值。转移时枚举上一个选的是什么,时间复杂度 O(n42n)

设字典序第 i 小的字符串为 si,那么 lcp(si,sj)=minik<jlcp(sk,sk+1)。于是就可以一步步转移了,设 fi,S,j 表示考虑了前 i 条路径,选取的边集并为 S,且当前路径与上一个选择的路径的 lcpj 的答案。转移如下:

  1. fi1,S,jfi,S,min(j,lcp(si1,si)),预转移,处理一下 lcp

  2. fi1,S,j+lenijfi,SEi,leni(SEi=),表示选第 i 条路径,其中 Ei 表示第 i 条路径的边集,leni 表示第 i 条路径的字符串长度。

时间复杂度 O(n32n),需要滚动数组优化空间。字符串排序直接塞 trie 里就行了。

然后考虑 w{0,1,2},c=0

当所有 w2 时,每条边都是确定的,所以总共只会有 n(n1) 个串;而当存在 w=2 时,需要枚举边集的所有情况,这样可能会有 O(n22n) 个串。

实际上只会有 O(2n) 个串。考虑剥叶子,第一个次剥的叶子最多往外连 2×(21++2n1)<2n+1 个串,乘 2 是因为路径有向。而第二次剥的叶子最多往外连 2n 个串,以此类推,所以最后不会超过 2n+2 个串,即 O(2n)

然后优化一下转移,第一维完全可以不记,让 f 数组自转:

  1. fS,jfS,lcp(si1,si)(j>lcp(si1,si))

  2. fS/Ei,j+lenijfS,leni(EiS)

当然 i 还是要枚举的,这样就不担心空间了。考虑分析时间复杂度。

对于第 2 部分,考虑树上一条长度为 len 的路径 xy,它中间最多会产生 2len 个串,其中每个串都要枚举 2n1len 个超集,于是它们的总转移时间复杂度为 O(n2n)。有 O(n2)(x,y),所以总转移时间复杂度就是 O(n32n) 的。

对于第 1 部分,考虑只保留有用的 dp 状态。将每次通过第 2 步转移得到的 dp 状态为“关键状态”,由上面可知“关键状态”只有 O(n22n) 个,而每个关键状态经过第 1 步转移时第二维至少减 1,所以最多经过 n 次第一步转移,于是总转移时间复杂度就是 O(n32n) 的了!

大概需要精细实现,当然不那么精细也可以,毕竟卡不满。

之后考虑 c=1,即如何构造方案。

显然不能开一个 O(n4n) 的数组记录从哪转移过来的,不然就又退化了。

于是考虑用一个操作栈来维护有效的 dp 转移,之后从后往前推就可以得到方案了。

直接记录的空间复杂度为 O(n32n),不可接受。考虑再分析一下有效状态数。

对于转移的第 2 部分,容易发现在枚举超集 S 后可以不用对每个 fS/Ei,j 都尝试转移 fS,leni,直接取最大值记录在栈里就行了,空间复杂度 O(n22n)

而对于第 1 部分,考虑树上一条长度为 len 的路径 xy,最坏情况下中间会有 2len 个串,其分布在 trie 树上的第 len 层。模拟一下这些串在第 1 部分贡献的转移复杂度,发现在遍历每个串的时候会先转移到所有 fS,len,其中 S 为这条路径的超集;之后根据先前的复杂度分析,这些 fS,len 最多在第 1 部分转移 n 次,于是就有先前分析的复杂度。

实际上,对于 trie 树第 len 层的前两个节点,遍历完它们之后,它们转移到的“关键状态”是完全相等的!换句话说,由于 dp 转移是取 max,所以对于每个 S,这两个节点对应的“关键状态”都是 fS,len,通过取 max 合并到一个状态了,于是它们总的复杂度贡献是 n

同理,对于 trie 树上第 i 层的一个节点,它下面有 2leni 个第 len 层的节点,这些节点对应的“关键状态”在第二维减到 i 的时候就全合并成一个 dp 状态了(对不同的超集 S 而言)。于是实际上 xy 的这些串在第 1 部分贡献的总复杂度为它们在 trie 上的虚树大小(对不同的超集 S 而言),即 O(2len)。而总共有 2n1len 个超集,于是时间复杂度就是 O(2n),所有 (x,y) 的总时间复杂度就是 O(n22n),空间复杂度也就变成 O(n22n) 的了。

时间复杂度 O(n32n),空间复杂度 O(n22n)。实际上栈的大小大概开 2×107 就够了,然后时间空间复杂度写得没那么精细也能过。


出题人到此为止了,但我们觉得还不够!

不难发现现在唯一的复杂度瓶颈在于第 2 步转移,需要枚举超集 S 后再枚举 j[0,n1] 进行转移。但其实只需要对于每个 S,维护出 gS 表示 fS,jj 最大的 j 就行了,由于 dp 值只会变大,所以是很好维护的。时空复杂度 O(n22n)

不难精细实现。栈的大小开 3×106 就够了。

一些剪枝:对 trie 树上每个节点的边集去重;不难证明一定存在最优方案使得每条链都有端点是叶子。

D6T3 落日珊瑚

咕。

Day 7

这场赢麻了。

D7T1 不是这一道据数构结题

不难发现操作相当于找出当前位置往后的前缀最大值,将它们循环右移。先考虑排列怎么做,这是个较为经典的东西。

(打表)观察每个数在每一轮中的位置变化,可以发现它在前若干轮不动,后面开始向后移动,直到它成为后面的最大值。进一步(打表)观察,设这个数前面有 K 个数比它大,那么它在前 K 轮都会保持不动,之后会一直往后跳到原排列上下一个比它大的数所在位置。

这是很好证明的。对于前 K 轮,前面有比它大的数,所以它不会被操作;而每一轮过后都会有一个前面比它大的数跳到它后面或已经排序归位,所以 K 轮之后它会成为前缀最大值。而之后它会不停跳到下一个比它大的数所在位置,不难证明这等价于原排列上下一个比它大的数所在位置。于是再转换一下,对于每个数 x,找到所有 x 的位置,那么 x 在第 i 轮前就在其中的第 i 个位置,与 x 初始位置取个 max

直接算答案不太好算,考虑从反面计算答案,求有多少轮是空的。这相当于第 i 轮需要排序的数 x 刚好在第 i 位上,根据上面的理论,这等价于所有 x 的数都挤在前 i 个位置。之后就好算了。

考虑扩展到序列。把同一种数 x 放到一起考虑,那么类似地,找到所有 x 的位置,所有 x 一定在这些位置上移动,且在前若干轮保持不动,之后每次拿出第一个 x,放到它下一个没被 x 占的位置。

还是从反面考虑,类似地,第 i 轮是空的当且仅当前 i 位的所有数 x,其中 x 是这一轮要排序的数。于是就有一个 O(nq) 的做法。

考虑固定左端点 l,移动右端点 r 的情况。对每一x 计算贡献,钦定在它前面且和它相等的数排序排在它前面,在它后面且和它相等的数排序排在它后面,那么它能贡献的右端点在一段区间内。具体地,设它所在位置 pos,那么当 rpos 时它才可能产生贡献,设在 [l,pos]x 排在第 k 位。由于 r 每次往右移都会新加一个数,所以若新加的这个数 >x,则会使 k 加一;否则不变。因为 x 轮空当且仅当 [l,k] 的数都 x,所以合法的 k 肯定在一段区间内,对应的 r 也在一段区间内。

发现 r 所在区间和 l 没太大关系,只需要保证 [l,pos]x 即可,于是可以得到 lr 的范围:l[Lpos,pos],r[pos,Rpos]Lpos 即为 pos 左边第一个比它小的数位置 +1;考虑 pos 往右的极长 x 连续段长度,设它为 len,那么 Rpos 即为它右边第 len>x 的数位置 1

之后就是二维数点了,时间复杂度 O(nlogn)

D7T2 意念力

先考虑链的部分分。划分成 m 个集合可以看作 m 染色,那么从前往后染色,第 i 个点不能与前面距离它 <k 的点同色。假设有 ai 个点,那么它的染色方案就是 mai,答案就是 i=1n(mai)

当然这算的不是恰好 m 种颜色的方案,最后容斥一下就行了。

w=1 的时候答案是很好计算的,而不保证 w=1 的时候可以看作给定多项式 i=1n(xai),求 x=1m 的点值。前面求多项式可以分治 NTT,后面可以用多项式多点求值,时间复杂度 O(nlog2n)

考虑一般树的情况。假设 w=1,那么按照 bfs 序给点染色,一个点不能与前面距离它 <k 的点同色。有一个关键性质是 bfs 序在它前面且距离它 <k 的点两两之间距离也 <k,根据 bfs 树的性质不难证明,于是也可以转化为求 i=1n(mai)

当不保证 w=1 的时候,也可以类似的按照到根距离排序,同样有上面所说性质(可以看作每条边挂了 w1 个点)。用点分治对每个点求出有多少点 bfs 序在它前面且距离它 <k,后面继续分治 NTT+多项式多点求值即可。时间复杂度 O(nlog2n)

小小吐槽一下:出题人出经典结论就算了,怎么还套了个多项式多点求值,让我这种不会的人很难办啊。

D7T3 重排

Largest Smallest Cyclic Shift

原题部分不讲了,网上都有。

考虑现在需要维护的操作:取出字典序最小和最大的字符串,把它们拼接起来。用 multiset 可以做到 O(|S|2log|S|)

一开始每个字符串都是一个字符,将它们按照字典序排序,相同的看作一段,那么每次操作可以看作取最后一段,将其中每个字符串“均匀”地接在第一段的每个字符串后面。之后第一段可能会出现两种不同的字符串,将较大的那些字符串单独分裂成一段即可。不难发现新分裂出去的段一定比后面的其他段字典序要小,于是只需要接在第一段后面。

具体实现直接用链表维护连续段,不需要真的拼接字符串,而是对每个位置维护它会被拼接在哪个位置后面。时间复杂度 O(|S|)

Day 8

D8T1 基础寄术练习题

神仙组合意义题。

先考虑 k=1 如何做。尝试用组合意义刻画 1i=1nsi:现在有 n 堆球,第 i 堆球有 ai 个球,总共就有 i=1nai 个球;将它们打乱,设 ri 表示第 i 堆球最右边的球所在位置,考虑计算 r1<r2<<rn 的概率。

这是容易计算的,按顺序加入每堆球,加入第 i 堆球的时候需要保证最后一个球属于这堆球,于是方案数为 (si1ai1)。总方案数为 (siai),于是概率为 (si1ai1)(siai)=aisi,乘起来得到总概率为 i=1naii=1nsi

由于 ai 互不相同,所以对一个 ai 集合 S 考虑:它有 n! 种排列方式,每种排列方式的概率之和为 1,于是就有 {ai}=Si=1naii=1nsi=1。又由于它们的 i=1nai 相同,所以有 {ai}=S1i=1nsi=1xSx

于是现在只需要求所有值域为 [1,m] 且大小为 n 的集合 S1xSx 和了,O(nm) dp 即可。

k=2 时,也可以用类似的思路。先枚举 a1,然后再枚举 a2n 的集合 S,由于 a1=s1,所以它们的概率和即为 i=2naii=2nsi,其中 i=2nai 相同。

k=1 时,它们的概率和恰好为 1;而在 k=2 时,它们的概率和则为 r1 恰好是 r 中最小值的概率。直接计算不好做,考虑容斥。

我们钦定集合 TS 内的 rr1 小,然后计算方案数。首先 T 里面可以随便排,记 sum=xTx,那么方案数为 sum!xTx!;然后把 a1 个球插进去,要求最后一个球在第一堆里,方案数为 (a1+sum1a11)=(a1+sum1)!(a11)!sum!;最后把剩下的若干堆球插进去,没有限制,方案数为 (i=1nai)!(sum+a1)!×xSTx!。把三个数乘起来可以得到 a1(i=1nai)!(a1+sum)i=1nai!,最后再除以总方案数就可以得到概率为 a1a1+sum,于是集合 S 的概率和即为 TS(1)|T|a1a1+xTx

于是就可以 dp 了,设 fi,j,k,0/1 表示已经考虑了值在 1i 的数,选了 j 个数在 S 里,T 内元素与 a1 的和(即分母)为 k,且当前有没有选出数当作 a1。转移是 O(1) 的,时间复杂度 O(n2m2)

D8T2 【模板】矩阵快速幂

考虑 k 在极大的时候路径长什么样,不妨猜测一下它会在某一个固定环上一直绕圈圈,感觉一下这很对。

事实上可能的最优路径一定形如,先走不超过 n2 步,再在某个最优比例环上绕圈圈,最后走不超过 n2 步。定义 x 的最优比例环为某个经过 x 的,边权和与长度之比最小的环。

证明可以考虑调整法。考虑前面的 n2 步,那么根据抽屉原理,一定能在路径中分离出 n 个相互独立的简单环;然后找到最优比例环,设其长度为 k,那么根据抽屉原理,一定可以从这 n 个环中选出一个非空子集使得其环长是 k 的倍数,将其替换成最优比例环一定不劣。后面同理。

于是只需要先花 O(n2m) 的时间求出 1 到每个点的经过 i(in2) 条边的最短路,再花 O(n2m) 的时间对每个点求出经过它的最优比例环,然后快速计算转环,最后再花 O(n2m) 的时间处理出每个点的答案就行了。

具体实现可以用高精度,也可以用 ak+bc 的方式存储最短路。注意 k2n2 的时候暴力即可。

Day 9

D9T1 最短路求和

数据范围 mn1000,不难想到广义串并联图。

一度点比较好缩,关键是二度点。先把所有 3 度点提取出来,跑出两两之间的最短路,然后再考虑二度点的最短路。

所有二度点形成一条条挂在两个三度点之间的链,于是考虑两条链 (a,b),(c,d)(a,b) 上一点 x(c,d) 上一点 y 的最短路就是 disx,y=minu{a,b},v{c,d}disx,u+disu,v+disv,y,容易 O(1) 计算。

不过显然不能 O(n2) 枚举点对,于是考虑一次性求点 x 到链 (c,d) 上所有点的距离之和,观察到链 (c,d) 上存在分界点 p,使得 (c,p) 间的点到 x 最短路都经过点 c(p,d) 间的点到 x 最短路都经过点 d。于是双指针一下即可,指针的总移动次数是 O(lena,b+lenc,d)

c=mn,由于链的个数是 O(c) 的,所以每个 lena,b 只会被算 c 次,总复杂度为 O(c2logc+nc)

D9T2 通道建设 Passage Construction

咕。

D9T3 Tree Topological Order Counting

如果只对一个点 u 求,那么可以拿出 1u 的路径,从前往后 dp。

对每个点求也是类似的,直接从上往下 dp。设 fu,i 表示考虑了 1u 路径上的点和 u 上方的分支子树,u 的拓扑序在第 i 个的方案数。

每次从 u 转移到 v,相当于先插入 u 的其它子树,再把 v 插进去。设 u 的其它子树为 v1,,vm,那么一开始有 nsizu+1 个点,要在 i 后面插入 sizv1 个数,方案数为 (nsizu+1+sizv1sizv1),再乘上 gv1 表示 v1 子树内的拓扑序方案。于是有转移 fu,i×(nsizvinsizu+1i,sizv1,,sizvm)×i=1mgvifv,>i

于是可以 O(n2) 求出 dp 值。gu 是好求的。

Day 10

D10T1 雷同

当合并的二叉树形态确定时,磨损值的贡献固定,所以一定是按照叶子深度从大到小排序后,将 wi 从小到大排序一一匹配。

而当叶子的 dep 序列固定后,重量的贡献固定,考虑何种树的形态会使磨损值的贡献最小。

不难发现磨损值的贡献即为将二叉树长链剖分后,除去最长链的所有 2len1 之和,由于链的个数即为叶子个数 n,所以可以看作是除去最长链的 2len 之和,最后减去 n1 即可。

从下往上考虑,设当前深度所有节点的子树深度最大值为 di,那么确定树的形态可以看作给这些节点两两匹配,若 i 匹配 j,则它们磨损值的贡献即为 2min(di,dj),合并的点的 d 值即为 max(di,dj)+1

不妨设 didj,那么上述合并操作可以看作 i “截断”了 jj 在此处将 2dj 贡献给答案,而 i 则会继续往上延伸。不难发现截断 d 较大的点比截断 d 较小的点更优,于是一定是将 d 从大到小排序后相邻两个匹配,第 1 大匹配第 2 大,第 3 大匹配第 4 大,以此类推。

于是最终树的形态一定是从左到右叶子深度递减,这就很好看了。继续简化磨损值的计算,对于某一层,假设它有 x 个从下面上来的节点,y 个新的叶子节点,那么这些新的叶子节点所在重链对答案的贡献即为 lowbit(x)++lowbit(x+y1)

从下往上 dp,设 fi,j 表示目前已经加了 i 个叶子,当前层已经有了 j 个节点。转移要么在这一层新加一个叶子,fi,j+lowbit(j)fi+1,j+1;要么转移到上一层,fi,j+w1ifi,j/2,其中 w 已从小到大排好序。时间复杂度 O(n2)

Day 11

咕。

Day 12

D12T1 这不是一道数据结构题

咕。

D12T2 不跳棋

容易想到点分树,于是直接建出点分树后暴力枚举祖先更新答案。

对每个点维护出子树内的最小深度和次小深度即可。由于强制在线,所以可以给每个点开一个大小为子树内最大深度的桶,维护一个指针,删除的时候往后扫就行了。

同理,对答案开一个桶,每次更新一个点的时候把它原来的答案删掉,把新的答案加进去。由于答案只会变大,所以对于全局答案也是维护一个指针就行了。时间复杂度 O(nlogn)

D12T3 goods

不难发现题目相当于对每个 j[xByj]i=1n(1+(1+x)2yai),其中 x 这一维是加法卷积,y 这一维是异或卷积。

考虑更一般的形式:i=1n(1+xyai),使用 FWT,那么根据定义式,有 [yj]FWTi(x,y)=1+(1)|j&ai|x,每一位乘起来便有 [yj]FWT(x,y)=i=1n(1+(1)|j&ai|x),之后 IFWT 回去就行了。

(1+x)2 带进上式的 x 便有 [yj]FWT(x,y)=i=1n(1+(1)|j&ai|(1+x)2),后面只有两种取值,即 x2+2x+2x22x,于是 [yj]FWT(x,y) 就可以表示为 (x2+2x+2)tj(x22x)ntj,其中 tj 表示有多少个 |j&ai|mod2=0

tj 是好求的,即求 [yj]FWT(i=1n(1+yai)),最后除以 2 就行了。

由于只需要求 xB 项,所以只需要对每个 j[xB](x2+2x+2)tj(x22x)ntj

[xB](x2+2x+2)tj(x22x)ntj=(1)ntj[xBn+tj](x2+2x+2)tj(x+2)ntj=(1)ntji=0tj2i(tji)[xBn+tj](x2+2x)tji(x+2)ntj=(1)ntji=0tj2i(tji)[xBn+i](x+2)ni=(1)ntji=0tj2i(tji)(niBn+i)22n2i+B=(1)ntji=0tj(tji)(niBn+i)22ni+B

然后发现只有和 i,tji,tj 有关的项,NTT 就行了,时间复杂度 O(m2m+nlogn)

Day 13

D13T1 天空度假山庄

可以把题意转化为求 12 长为 k 的路径,23 长为 k 的路径,……,n1n 长为 k 的路径,要求不重复经过边。这是因为原图是完全图,点的标号显然没有关系。

然后我做了如下尝试:能否只构造出一条 12 长为 k 的路径,然后把它轮换一下得到后面的路径?

这样限制就变成了对于路径的每条边 uv,要求不能有另一条边 uv 使得 vuvu 相同或相反。

这等价于构造一个长为 k 的序列 {ak},其中 ai 表示第 i 条边的 (vu)modn,要求 ai[1,n1] 且任意两个数不能相等或相加等于 n,并且它们的和 modn=1。随便构造即可。

kmod4=1(1)(2,3,4,5)(6,7,8,9)

kmod4=2(1,2)(3,4,5,6)(7,8,9,10)

kmod4=3(1,2,4)(5,6,7,8)(9,10,11,12)

kmod4=0(1,2,3,5)(6,7,8,9)(10,11,12,13)

由于保证了 n2k+15,所以可以满足条件。时间复杂度 O(nk)

Day 14

D14T1 命运

相当于从大到小排序后字典序最小。重边没用,先把重边去掉保留边权最小的;之后按边权从大到小尝试删除每条边,能删就删。

如何判断删完之后图合不合法:

  1. s 点的度数 k
  2. 整张图连通;
  3. 去掉 s 后连通块个数 k

用类似分治的算法,每次先递归右半部分,这时左半部分所有边都是加入的;确定完右半部分哪些边可以删除后再递归到左半部分即可。用可撤销并查集做到 O(mlogmlogn)

Day 15

D15T1 括号

首先观察到操作的括号一定形如左边一段 ) 右边一段 (;然后分析一下就会发现操作的所有 ) 都在前缀最小值左边,操作的所有 ( 都在前缀最小值右边。

于是问题变成了在前缀最小值前面选若干个 ) 变成 (,使得前缀最小值 0;后面同理,下面只考虑前面部分。

有若干个限制,每个限制形如 x 的位置至少要选出 y 个数,且 yx 递增而递增。转化问题,看作有若干个位置有 1,要选出若干个 +1(即原串的 )),使得每个 1 都能和 +1 匹配(匹配即为 +11 前面),也即它们形成合法括号串,使得代价和最小。

画出折线图,我们需要时刻保证所有后缀和 0。一开始只有 1,考虑以任意顺序加入 +1

  1. 如果它后面有一个还没匹配的 1,那就直接匹配。等价于加入这个 +1 后仍保证所有后缀和 0
  2. 否则可能需要替换掉之前已经匹配好的某个 +1。考察它能替换掉哪些位置,首先在它右边的肯定可以;然后对于它左边的位置,需要保证替换掉之后仍保证所有后缀和 0,于是设 p 表示它左边最近的后缀和 =0 的位置,它就只能替换掉 >p 的位置。

之后再考虑删除任意一个:

  1. 如果它没被匹配,那什么都不用管;
  2. 否则可能会用另一个没被匹配的位置替换掉它。考察它能被哪些位置替换,首先在它左边的肯定可以;然后对于它右边的位置,需要保证替换掉之后仍保证所有后缀和 0,于是设 p 表示它右边最近的后缀和 =0 的位置,它就只能替换掉 p 的位置。

修改相当于先删除再加入。用线段树维护一下后缀和(前缀和等等也行),然后还需要另一棵线段树维护哪些位置被选了/没被选,代价最小值/最大值是什么。时间复杂度 O((n+q)logn)

Day 16

D16T1 最后的晚餐

考虑一个无限长的序列 0,1,2,3,,然后每十个分一段。对于一个固定的可重集合 A,相当于要以任意顺序排列 A,之后从 0 出发,每一步往右跳 ai 步,使得被跳到的块尽可能多。

为了方便,下面求的都是 f(A)1一个显然的上界是 sumA10,相当于中间不能有任何块被跳过。先考虑 ai11,那么跳过某一块只有可能是当前站在了 9 且下一步跳了 11。于是不难得到一个贪心策略:如果当前站的位置 =9 就任选 10 的数跳,如果当前站的位置 8 就优先选 11 跳。

问题就在于如果 11 太多,把 10 的都耗光了,那下次站在 9 的时候必须选 11 了。不过注意到这种情况下按照上面的规则每跳一次必定会进位,所以它的答案会顶到另一个上界 |A|。于是有 f(A)1=min(sumA10,|A|)

当存在 ai=12 时,也可以仿照上面的策略,在当前位置 =9 的时候任选 10 的数跳;在当前位置 =8 的时候选 11 的数跳,优先选 11;在当前位置 7 的时候优先选 12,再优先选 11

不过存在一个问题,就是当最后只剩 12 的时候,不能保证前面全都进位。具体而言,如果当前位置为 8,并且我选了 1 去跳,那这个时候是不进位的。

于是修改一下贪心策略:当前位置为 9 的时候优先选 1;当前位置为 8 的时候尽量不选 1

考虑如何快速计算此时答案,即最小化不进位次数。由于此时 12 是充裕的,所以只需要考虑当前位置为 89 的情况。首先 12 是个偶数,所以第一次到达的位置是 8 而不是 9;然后如果选了奇数跳,那么会改变下一次到达的奇偶性;否则不变。如果当前位置为 8 且选了一个 >1 的奇数,那么下一次到达的位置为 9,选 1 即可。如果当前位置为 8 且选了 1,那么下一次到达的位置为 9,选 1 即可,贡献一个不进位。于是最优策略是尽可能的把 1 和其他奇数配对,不够的再自行两两配对。

ci 表示 i 的出现次数,那么不进位次数的最小值为 max(0,c1(c3+c5+c7+c9+c11)2)。于是有 f(A)1=min(sumA10,|A|max(0,c1(c3+c5+c7+c9+c11)2)

之后考虑对所有子集计算答案。先将偶数,1>1 的奇数分开,对于偶数和 >1 的奇数,分别求出 f(n,s)g(n,s) 表示选了 n 个数且和为 s 的方案数。前缀和优化转移即可。于是答案变为 c1,n1,s1,n2,s2f(n1,s1)g(n2,s2)min(s1+s2+c110,n1+n2+c1max(0,c1n22))

考虑将 1>1 的奇数合并,即 g(n2,s2)h(n2+c1max(0,c1n22),s2+c1)=h(n2+min(c1,n2+c12),s2+c1)。分成 c1<n2c1n2 两段后差分一下即可。

之后答案变为 n1,s1,n2,s2f(n1,s1)h(n2,s2)min(s1+s210,n1+n2),把 min 拆开之后双指针一下即可。s1+s210 可以拆成 s110+s210+[(s1mod10+s2mod10)>9],分 mod10 讨论一下就行了。

时间复杂度 O(n2a2),可以通过卡枚举上下界获得更快的运行速度。记得最后把 f(A)11 加回去。

Day 17

D17T1 棋盘

先算了一下二进制和六进制(即 2×23×3),发现都差一点。然后更大的显然就不行了。

于是考虑一些奇奇怪怪的形状,比如 2×3。发现这是三进制,也不太行;但是如果让两个矩形相交两个格子,就变成了斐波那契数列。

写个高精度算一算发现需要 480 项,不过可以除个 2,刚好是 240。然后算一算发现刚好需要 958 个格子,于是做完了。

D17T2 区间切割

暴力显然就是按时间顺序模拟切割,每次选择长度大的那一边。如果是随机数据,那么感觉一下每个区间不会切割很多次,如果能对每个区间快速找到哪些切割时有效的就行了。

先将修改离线下来,然后按 1n 顺序求解,每次碰到 l 就加入一次切割,碰到 r 就删掉。对于区间 i,只需要不断找到 [Li,Ri] 之间最早被切割的位置,切割就行了。切割位置在最左或最右边可能会有点问题,但是不难发现最左或最右边切割没用,所以只需要找 (Li,Ri) 之间的位置。随便拿线段树维护就行了。

如果不是随机数据,那么一个区间可能会被切割很多次,不好一个个模拟。考虑能不能快速求解很多次操作,比如每做完一次区间砍半之类的。

具体来说,我们知道一次切割会保留较长的一边,那么对于极左的切割位置,肯定会把左边切掉;右边同理。如果只是取中点之后默认左边的切左边,右边的切右边,那显然是错的。但如果取的是三等分点,那就可以保证在切到中间之前,左边的一定切左边,右边的一定切右边。

拿线段树维护一下,每次取出中间段最早被切割的位置,线段树二分求出它切割之前左边和右边分别切到了哪,时间复杂度 O(nlog2m+mlogm)

本文作者:AFewSuns

本文链接:https://www.cnblogs.com/AFewSuns/p/17803920.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   AFewSuns  阅读(1671)  评论(7编辑  收藏  举报
历史上的今天:
2021-11-20 NOIP2021 游记
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起