CodeChef 2020 July Long Challenge 题解
第一次打 CodeChef Long Challenge。
居然阿克了?(当然除了 challenge 题)
然而中途几题还是想了好久,我好菜啊……
最后几天被叫去 whk 了,最后 challenge 连 AC 都没拿到。(虽然拿到了也还是 rk2)
还是 Div.2,菜死了。
CHEFSTR1
模拟。
CRDGAME
模拟。
ADAKING
前 \(k\) 个格子空着,其它格子有障碍,然后左上角放王就行了。
PTMSSNG
算是经典题的二维版本吧。
对于每个 \(x\),都应该出现了偶数次。若出现了奇数次,那么缺失的点的 \(x\) 坐标就是它。
可以用别的方法,但是把所有 \(x\) 异或起来的结果就是答案的 \(x\) 坐标。
\(y\) 同理。
时间复杂度 \(O(n)\)。
CHFNSWPS
首先,每个数在 \(A,B\) 里总共出现的次数都应该是偶数。否则无解。
然后我们就知道 \(A\) 要把哪些数给 \(B\),\(B\) 要把哪些数给 \(A\)。
我们要把 \(A_i\) 给 \(B\),\(B_j\) 给 \(A\),有两种情况:
- 直接交换,代价 \(\min(A_i,B_j)\)。
- 找到这两个序列中最小的数 \(x\),不妨设 \(x\) 在 \(A\) 中,那么 \(x\) 和 \(B_j\) 交换,\(x\) 和 \(A_i\) 交换,发现 \(x\) 还在 \(A\) 中。代价 \(2x\)。
有没有可能有别的情况,比如选多个 \(A\) 给 \(B\) 和 \(B\) 给 \(A\),然后有一起操作的好方法?
没有。简单证一下:
首先若一个 \(A_i\) 到了 \(B\),肯定不会回到 \(A\)。所以可以把 \(A_i,B_j\) 直接交换的扔出去(实际上是上面第一种情况)。
设剩下 \(2k\) 个数,\(A\) 要给 \(B\) 其中 \(k\) 个,\(B\) 要给 \(A\) 其中 \(k\) 个。
剩下的每次操作都只可能还原一个数(用不是这 \(2k\) 个数的和它交换),所以至少要 \(2k\) 次操作。而一次交换最小代价是 \(x\),总代价最小就是 \(2kx\)。那么进行 \(k\) 次上面的第二种情况是等价的。
那么问题就是配对 \(A_i\) 和 \(B_j\),使得 \(\min(A_i,B_j,2x)\) 的和最小。
若我们将 \(A\) 要给 \(B\) 的数从小到大排序为 \(C\),\(B\) 要给 \(A\) 的数从小到大排序为 \(D\),个数都为 \(k\),那么一定是 \(C_i,D_{k-i+1}\) 配对。感性理解就是对于一个很小的 \(C_i\),一定要把一个很大的 \(D_j\) 拖下水,防止有更大的花费。
时间复杂度 \(O(n\log n)\) 或更优秀。
DRCHEF
感觉好神仙啊,为什么那么多人会做啊 /kk
可能又是杀鸡用牛刀了,欢迎来吊打这个做法 /kel
(其实这个做法要想到不难,但是正确性可能有点难证)
免责声明:以下的“杀光了”只是一种便捷表述,并不与题面里任何病毒有关事物的挂钩。
显然,若对一个 \(>2x\) 的数 \(a\) 操作,那么对整个局面唯一的影响是 \(x\) 变为 \(2x\)。
现在考虑所有 \(a\) 相等怎么做:
若 \(x\ge a\),那么可以一个一个进行操作,每次操作都能杀光一个,答案为 \(n\)。
若 \(x<\frac{a}{2}\),那么肯定需要花一次操作将 \(x\) 变为 \(2x\)。
若 \(\frac{a}{2}\le x<a\),那么操作一次不能杀光任何一个,但是之后就能随便杀。
综上,答案为 \(n+\max(0,\lceil\log_2\frac{a}{x}\rceil)\)。
注意到上面有一步十分优秀:那就是一个一个进行操作,全部杀光。
考虑所有 \(\le x\) 的 \(a_i\),从大到小一个一个杀,就能做到这种效果。
不妨先将 \(a_i\) 从小到大排序。
现在我们有一个较优解(注意不一定是最优):不停对 \(a_n\) 进行操作,直到 \(x\ge\frac{a_n}{2}\)。此时我们对 \(a_n\) 再进行一次操作:
- 若此时 \(a_n\) 被杀光了,那么 \(x\) 一定大于所有的数,再花 \(n-1\) 次操作即可。
- 若此时 \(a_n\) 没有被杀光,变成了 \(a_n'\),且 \(a_n'\ge\frac{a_{n-1}}{2}\),那么对 \(a_n'\) 再来一次操作后(此时 \(n\) 被杀光了),\(x\) 仍然不小于 \(a_{n-1}\),就可以花 \(n-1\) 步杀光剩下的。所以总共要再花 \(n\) 步。
- 若此时 \(a_n\) 没有被杀光,变成了 \(a_n'\),且 \(a_n'<\frac{a_{n-1}}{2}\),那么对 \(a_{n-1}\) 来一次操作(此时 \(n-1\) 被杀光了),\(a_n'\) 翻倍后仍然不大于 \(x\),也可以花 \(n-1\) 步杀光剩下的。所以总共要再花 \(n\) 步。
我们得到了 \(n+\max(0,\lceil\log_2\frac{a_n}{x}\rceil)\) 步的操作。(其实就是操作直到 \(x\ge a_n\),最后再用 \(n\) 步)
注意到对于 \(i\ne n\),如果不是为了一下杀光它,就根本不会动它。因为无论之前多动多少轮,都需要 \(x\ge a_i\) 才能最终把它杀光,而之前动 \(n\) 不会让 \(x\) 变小。
上面这个做法哪里不够优秀呢?因为最后我们要花 \(n-1\) 步杀光剩下的数,但实际上可以在把 \(x\) 变大的中途顺便杀掉一些。
我们肯定是选择某些时刻,找到最大的 \(\le x\) 的 \(a_i\)(如果有),然后把它杀光。如果 \(a_i\) 足够大,就可以让 \(x\) 变小的值不足以影响答案。
但是有没有可能找到最大的几个 \(\le x\) 的 \(a_i\) 杀光呢?
不可能。多杀一个会使得 \(x\ge\frac{a_n}{2}\) 的步数变多至少 \(1\),而至多让后面杀光全部的代价变少 \(1\)。
所以我们设 \(f_i\) 表示我们上一个顺带杀光了 \(a_i\)(所以此时 \(x=2a_i\))的最小代价。提前把多杀的个数减掉,后面集体杀光的时候就不管了。
转移,\(f_i=\min(f_j+\max(0,\lceil\log_2\frac{a_i}{2a_j}\rceil),\max(0,\lceil\log_2\frac{a_i}{x}\rceil))\)。注意不需要考虑往前再多杀几个了。
统计答案时,枚举最后一个顺带杀光的是哪个(如果有,没有就直接从 \(x\) 开始),简单算。(其实如果把式子写出来会发现就是 \(f_n+n\),虽然 \(f_n\) 本身的意义不明确)
注意到 \(\log_2\) 的取值只有 \(\log\) 个,同时注意到 \(f_i\) 单调不减,分段转移,每一段取左端点转移即可。
时间复杂度 \(O(n\log a_i)\)。
DRGNDEN
感觉比前两题简单多了。
如果从 \(i\) 到 \(j\) 的路上有 \(k\) 阻挡(注意到一定有 \(h_k>h_j\)):
- 如果 \(h_k<h_i\),由于权值都是正的,不如从 \(i\) 到 \(k\) 再到 \(j\)。
- 如果 \(h_k\ge h_i\),那就是走不过去。
所以注意到如果往右走到 \(j\),那么一定有 \(pre_j=i\),其中 \(pre_i\) 表示 \(i\) 前面比 \(h_i\) 大的最后一个。
往左走同理。
注意到 \(pre\) 形成了森林,如果加一个超级根,那么问题就转化为给定一棵树,支持操作:
- 单点修改;
- 给出 \(u,v\),若 \(u\) 不是 \(v\) 的祖先则无解,否则求链上点权和。
树剖可以做到 \(O(n\log n+q\log^2n)\),树上差分可以做到 \(O((n+q)\log n)\)。
LCMCONST
显然每个质因子互相独立,拆开看,问题就变成了限制 \(\max(b_u,b_v)=w\)。
先求出每个 \(b_i\) 的上界 \(upr_i\)。
如果存在一个 \((u,v,w)\) 满足 \(w>upr_u,w>upr_v\),则无解。
否则,如果存在一个 \(i\) 满足 \(upr_i=+\infty\),则无数个解。
否则一定有限且不为零个解(让每个 \(b_i=upr_i\) 即可)。
先考虑 \(O(n2^n)\) 怎么做(别容斥,容斥就没救了):
枚举 \(b_i\) 是不是 \(upr_i\),通过前面的信息就可以推出来当前的 \(b_i\) 是否一定得是 \(upr_i\)。
数据范围提示很明显折半。
枚举后半部分的状态,就能知道前半部分哪些得顶到上界,设为 \(S\),让 \(a_S\) 加上这个后半部分的方案数。
再枚举前半部分的状态,看有多少个后半部分的方案能满足它。设前半部分状态是 \(T\),那么后半部分的方案数是 \(T\) 的所有子集的 \(a\) 的和。
对 \(a\) 进行高维前缀和即可。
时间复杂度 \(O(n2^{n/2}pcnt)\)。\(pcnt\) 是不同的质因子个数。
EXPREP
感觉再次杀鸡用牛刀了 /fad
对于左右端点是 \(i,j(i<j)\) 的串,会贡献 \((pre_j-pre_{i-1})(lcs(S[1\dots i-1],S[1\dots j])+1)\)。\(lcs(S,T)\) 表示串 \(S,T\) 的最长公共后缀长度。
\(lcs+1\) 拆开,\(1\) 的部分随便做,\(lcs\) 的部分,先特判 \(i=1\)(其实就是 \(0\),不管它了),要算这个:
我们建出这个串的 SAM。说好打死都不用 SAM 的呢?真香!
在 parent 树上,求出每个子树里有哪些前缀。合并多个儿子时,就看别的儿子有多少个比它小的加上。
用线段树合并可以做到 \(O(n\log n+n|\Sigma|)\)。
DYNHUL
场上思路:
怎么求给出的参数 \(m\) 的对应方案啊,不会,自闭了。
搞 whk 时思路:
我发现我是个 sb。
每次先建出凸包,如果要删的点不在凸包上肯定不优,只看凸包上。
枚举要删哪个点,看它旁边两个点,对这之间的点(当然除了目前枚举的点)求凸壳。这个凸壳就会替换掉目前这个点相邻的两条边。
时间复杂度显然是 \(O(n^2)\)。
然后求一个方案的代价,倒过来弄个动态凸包面积,\(O(n\log n)\)。
瞎退退应该能退个不错的成绩,不太清楚。