看了下赛程,大概只能打 Round 1 和 Round 2 了,之后可能就退役了。
Contest #1
打的时候因为有点事,大概只打了一个多小时。现在终于有时间补完了。
题对于知识点完备的选手比较简单。我显然不是这样的选手,做做就当学点东西了。
C、D、E 题代码可以翻 LOJ,其他三题有需要可以联系我。
A - Ljeto
直接模拟即可。
B - Kamenčići
博弈题。(nle 350),比较小,可以考虑一个大概不超过 (O(n^3)) 的 dp。
考虑一个状态有三个要素:左右端点和,以及共取了多少个红色石子(只需要记录一个人的,因为另一个以及可以据此确定)。
那么定义:(f(l, r, c)) 表示当前操作者面对的局面是,之前以及取过 (c) 个红石子,现在剩下了区间 ([l,r]),是否有必胜策略。
根据必胜必败态定理得到转移:(f(l, r, c) = lnot f(l+1,r, c') lor lnot f(l,r,c'))。(c') 是计算得到的对方取过的红石子数。
复杂度 (O(n^3))。据说可以分析性质得到 (O(n^2)) 解法,可以到 cf 看看。
C - Logičari
第一次写基环树题!做法可能不是最简洁的。
题意简述:对基环树黑白染色,要求每个点相邻恰好一个黑点。求最小可能黑点数。
先转化为正常的树:任意找一条环边 ((a, b)) 断开,然后钦定一下断开的边两端的四种颜色组合。
不妨设 (a) 为根,每个点四个状态,记录自己和父亲的颜色选择。然后做正常的树形 dp。
但是 (a, b) 两个点需要是特殊处理的。对于 (a),可以试做它的父亲为 (b)。而对于 (b) 需要额外讨论一下……具体细节就不展开了。
复杂度 (O(n))。
D - Set
一下所有数字默认减一(({1, 2, 3} o {0, 1, 2}))。
我们挖掘一下题目的性质:对于一个位的三个数字 ((a,b,c)),其所有合法组合对应的数字加起来都是三的倍数,即 ((a+b+c)mod 3 = 0)。
考虑到本质不同三元组个数为 (3^m),设 (f[i]) 表示 (i) 三进制表示对应的 (m) 元组的个数((f[i]in {0, 1}))。
题目要求选取的三个 (m) 元组 (i,j,k) 的每一位都满足三进制不进位加法((oplus_3))为 (0),那么就是 (ioplus_3 j oplus_3 k = 0)。
考虑卷积,下标用上面的 (oplus_3),那么最后 (f) 自己卷自己三次后 (f'[0]) 就是答案。可以用 3 进制 FWT 实现。
E - Volontiranje
有一个传统的流做法,但是应该只能跑 subtask 2。
考虑一个基于贪心的神奇结论:在所有备选 LIS 中,抽出一个反转后字典序最小的必然不劣。
首先感性理解比较真,意识流 证一下也不难:考虑有两组 LIS:({i_1,i_2, cdots,i_k}, {j_1,j_2, cdots,j_k}),那么我们对应项小的换到 (i),大的换到 (j),即用 ({min(i_1, j_1),min(i_2, j_2), cdots,min(i_k, j_k)}, {max(i_1, j_1),max(i_2, j_2), cdots,max(i_k, j_k)}) 取代之,不难发现其仍然合法。这个方法可以对一个解集在解之间做调整。但是如果最小的那个本身不被该解集包含,则我们可以不要这个解集,而必然存在另一个“可以向小调整”的解集。(看我题解很难没有疑问(草),想要靠谱点的可以看 cf 或翻官方题解)
然后尝试实现。直接一次 (O(n)) 扫一组解必然不对,考虑先做一次 dp 跑出每个位置结尾的 LIS,然后按 dp 值将下标分组。显然的,同一组的按位置升序排好之后,其值必然递减。
那么,如果现在需要选 dp 值为 (i) 的组中的位置,上一次选了位置 (x)。我们先删去在 (x) 之后的元素:易知这些元素在本次不可用,之后也不会起作用。
删掉一些后,查看最后面的位置 (y) ,是否可以接上 (x)。如果 (a_y > a_x),那么 (x) 就没办法用了。需要注意,这不意味着求解结束,而只是将 (x) 删去,(y) 可能在枚举字典序更大的一组解是被使用。因此使用搜索回溯实现。
每个位置用一次就会被删除,搜索部分复杂度 (O(n))。总复杂度 (O(nlog n))。
Contest #2
D、E 等 upsolve 出来再补了。
A - Kaučuk
签到
B - Kutije
首先转化题意,将给定的 (p_i) 转化为图上的 (i o p_i) 的有向边。然后每个询问相当于问是否存在 (u) 到 (v) 的有向路径。
观察到 (nle 1000),我们可以考虑预处理所有的答案。但这张图是稠密图,连做 (n) 次 DFS 的复杂度是 (O(n^3))。不过我们发现一次 DFS 每个点之后访问一次,但主要的时间消耗在于找到第一个没有访问过的相邻点。
于是不难想到用 bitset 加速寻找,复杂度 (O(n^3/w))。
C - Hiperkocka
构造垃圾终于瞎搓成功了!
首先为了让结构更清晰,我们将这些边分为 (n) 层,根据第 (k) 位不同产生的边放在第 (k) 层,每层有 (2^{n-1}) 条边。
根据直觉,我们想办法让这 (n) 条边的树的每一条都用掉一层的一条边,这样恰好每层用 (2^{n-1}) 次。
第一棵树是很好构造的,直接从位置 (0) 开始 DFS,每次 (O(n)) 遍历最小未使用的层。然后我们发现如果从 (1) 直接开始 DFS 构造第二棵树就可能冲突,不过根据观察,我们要避开的话,最好是从 (3) 开始。
这时你可能发现了,位置 (0) 和 (3) 除了上面几层用的边,其余都是对称的。这启发我们倍增地构造一组靠谱的起始点,使得直接在这些位置 DFS 就能出解(并不会证为什么可以这样,这里如果有人会证可以发个评论)。
考虑从 ([0, 4) o [4, 8)),如果同样用对应的位置 (4,7) 的话会冲突,而反过来用位置 (5, 6) 的话则恰好错开,同时 (5, 6) 构造出的树也是在低层对称的,同 (0,3) 一样也不会冲突。
那么不妨尝试如下算法:初始区间 ([0,2)) 中 (0) 为起始点,然后复制一份,将是否为起始点状态取反,接在后面。
然后对于每个起始点都搜出一棵即可。
D - Magneti
E - Osumnjičeni
草,赛后发现是 sb 题,但是 C 题搞太久了……
口胡,不知道对不对。
考虑答案的计算方法是确定的:对于询问 ([l,r]),我们从 (l) 开始,找到一个最大的 (ile r),满足 (lsim i) 号身高区间全部不交。那么下一次从 (i+1) 开始重复操作即可。最后答案就是操作的轮数(可能反过来再做一次)。
第一是预处理右侧最远的合法位置。考虑建立以身高(离散化)为下标的线段树,从 (1) 扫到 (n)。每次查询当前身高区间中的最大值,然后在这个区间做区间赋值。
第二是快速跳转。不难发现这个可以倍增加速。
最后复杂度 (O(nlog n))。