• codeforces 乱做


    开学初还蛮有空的,顺别提醒自己别太颓,想起去年区域赛毛都不会做的痛苦面具,感觉学东西容易降智,觉得还是趁着有空多练练binary search的技巧。

    在谷上发现了一个Codeforces 好题 Div.1-1的题单,打算先从这个开始做。

    CF472G Design Tutorial: Increase the Constraints

    大暴力。

    手压一个(64)位的( ext{bitset}),纯暴力做的话时间的上界大概是(frac{400000 imes 200000}{64}),感觉这个估算其实非常接近机器运行的指令数,所以是跑不过极限数据的。

    那咋办呢?对(b)分块,假设块大小为(S),有(frac{n}{S})块,利用(frac{n^2}{S})的空间预处理(a)的每一个位置开始与(b)的每一块匹配得到的答案,每一次查询的时间是(O(frac{S}{w} + frac{n}{S}))(S)可以取个(sqrt{wn} = 8sqrt{n})

    代码大概跑了(4s)左右。

    code

    看了下正解发现是分块fft,手动笑哭。

    CF461B Appleman and Tree

    看完一眼dp,然后做一年。

    感觉我这个dp水平实在是不行,只能看着小样例猜方程。

    (f(x, 0 / 1))表示以(x)为根的子树中没有 / 有黑色点的方案数,初值(f(x, c(x)) = 1),转移为

    [f(x, 0) = f(x, 0) * f(y, 1) + f(x, 0) * f(y, 0) ]

    [f(x, 1) = f(x, 1) * f(y, 1) + f(x, 0) * f(y, 1) + f(x, 1) * f(y, 0) ]

    主要是这种求方案数的题目要注意两边都可以的情况中间断开也是一种解(f(x, 1) * f(y, 1)),当时就感觉是一个容量为(2)的背包,就做不出来了。

    code

    CF455D Serega and Fun

    分块,每个块套一个( ext{queue})

    对于同一块中的修改和询问,将这个( ext{queue})里面的元素推平到数组里面暴力做,然后重构;否则,修改整块的时候相当于对这个队列进行一个( ext{pop})( ext{push})操作,顺便对每一块维护一个桶,(O(1))询问。

    极限数据为修改((1, n)q)次。

    块大小开大点可以有效加速。

    code

    可以用(n + 1)棵平衡树维护一下全局的序列和每一个值出现的序列,能做到(O(nlog^2 n))

    CF383E Vowels

    这种还是考虑计算每一个集合对应的答案。假设最后的元音集合为(s),有字母集合(t)的数量为(cnt(t)),容斥一下,

    [ans(s) = sum_{t subseteq s} (-1)^{|t| - 1} cnt(t) ]

    容易发现当(|t| > 3)(cnt(t))都是(0)

    考虑计算(g(s, 1/2/3)),表示(s)的大小为(1 / 2 / 3)的子集的(cnt)总和。(1)的情况很简单;对于(2)的情况,可以考虑(s)的所有与它大小相差(1)的子集(t),将所有的(g(t, ?))加起来,发现每一个合法的((s_i, s_j))都被计算了(|s| - 2)次,除掉即可;(3)的情况类似。

    这样的时间复杂度是(O(w2^w)),其中(w = 24),以cf的机器已经能跑过了,还有预处理的复杂度。

    正解是神奇的折半,实际运行时间应该和这个差不多。

    code

    话说写到这里还没一个是写的正解。

    CF372C Watching Fireworks is Fun

    这个比较简单。

    最后的答案可以写成(sum b_i - sum |a_i - x_i|)的形式,前面是一个定值,考虑求出后面那个东西的最小值。

    一个很自然的想法是直接dp,设(f(i, j))表示当前考虑了前(i)个烟花,最后停在(j)的情况,

    [f(i + 1, y) = min(f(i, x)) + |a_{i + 1} - y| (|x - y| leq d(t_{i + 1} - t_i)) ]

    后面那个对于特定的(i)(y)来说又是定值,于是变成求一个区间的最小值。仔细看看,发现这就是个滑动窗口,于是单调队列一下,(O(mn))过得非常轻松。

    这个终于是正解了。

    code

    CF434D Nanami's Power Plant

    这个是看题解的,洛谷上很少有写得这么清晰易懂的题解。

    第一篇题解

    第一个条件想到网络流,第二个条件想到差分约束,然后就整不会了;其实这种网络流的模型还是从最小割的角度考虑比较自然。

    我们可以暴力地将每一个函数的每一个点值的组合建一个点((i, j)(l_i leq j leq r_i + 1)),将((i, j))((i, j + 1))连一条权值为(lim - f_i(j))的边,建立超级源(S)向所有((i, l_i))连权值为(infty)的边,建立超级汇(T)将所有((i, r_i + 1))(T)连权值为(infty)的边。

    其中(lim)要超过所有函数(f)的最大值,然后我们对这个图求个最小割,显然每一个(i)的边集中会割掉恰好一条非(infty)的边,所以最后所求就是(n cdot lim - ext{mincut}(S, T))

    接下来考虑(x_u leq x_v + d)的限制,写成(x_u - d leq x_v),对于所有符合条件的((u, x_u)),可以向符合条件的((v, x_u - d))连容量为(infty)的边,表示割了((u, x_u))的出边就必须割掉((v, x_u - d))以后的出边。

    需要考虑的是那些(x_u - d)不合法的情况:如果(x_u - d < l_v),那么这个条件没影响;否则需要建立一个结点((v, ext{end}))((u, x_u))连到((v, ext{end}))上去,否则就会出现不合法的情况(参考第一个样例),本题中这个( ext{end})刚好取(r + 1),符合要求。

    还有一个高压线就是不要用(S)(T)连出来的边表示某个合法的割,参考上一条。

    点数算少了wa了一发。

    code

    话说nanami一般是翻译成七海吧,二次元的气息扑面而来啊,去看了一下果然是中国人出的。

    CF342E Xenia and Tree

    这个题一看就很点分啊。

    高中的时候就没有好好学点分,老年人要去补课了。

    将点分治的每一个分治中心记录下来,然后将上一层的分治中心和下一层的分治中心连边,就得到了一棵原树的重构树,这个东西就是点分树。容易看出这棵树的大小非常平衡,高度为(log n)

    观察修改一个点(x)的点权对朴素的点分治找答案的影响,其实只有(x)以上的分治中心中的一个答案变了,所以我们暴力跳点分树沿途修改答案就可以了。

    原来的点分在处理分治中心(x)时需要注意两个答案从同一个儿子里来的影响,这题求最小值,所以就根本不用考虑了,因为一定不优。

    code

    喜欢这种优雅的暴力

    CF11D A Simple Task

    一些接近正解但是死活过不去的错误做法会让人丧失思考的能力,这其实是非常可怕的。

    考虑怎么统计一个环,可以尝试从环上的编号最小点开始走,一直计算出到另一端的路径条数,这样的话每个环被恰好统计两次(最小点的左边断开和右边断开)。

    (f(s, x))表示从(s)的最低位(设为(y))出发,走过的集合为(s),最后到达(x)的路径条数,如果存在((x, y)),则将(f(s, x))统计入答案,枚举转移的点(z)时需要满足(z otin s, z > y, (x, z))存在。

    由于是无向图,需要规律所有的两元环(一条边)对答案的影响,所有的两元环在上述过程中被统计恰好一次,所以(ans = frac{sum_{ ext{合法的状态}} f(s, x) - m}{2})

    code

    CF19E Fairy

    首先这个仅不加入一条边的操作是一个经典分治,设(solve(l, r))表示不考虑([l, r])这个区间里的边的结果,那么每次加入(frac{r - l}{2})条边就可以将问题规模减少一半。

    然后考虑怎么在加边的过程中维护这个图是不是二分图,可以使用一个并查集,如果我们不做任何优化的话,原始的那个并查集的树形结构可以维护出两个点之间的边数的奇偶性。还顺便发现了如果((x, y))原来就相连并且中间的边数为奇数的话,那么我们加入((x, y))这条边也不会改变中间这个奇偶性,那我们一边连边一边检查就可以了。

    发现这题需要回撤并查集的操作,所以不能路径压缩,只能按秩合并,然后用栈记录所有的操作,回退的时候暴力弹栈即可。

    code

    然后把代码稍微改改就可以通过BZOJ4025 二分图。

    CF338E Optimize!

    新套路get

    首先需要注意的是匹配可以不按顺序。

    条件转化一下

    [a_i + b_j geq h Leftrightarrow a_i geq h - b_j ]

    (b_i' = h - b_i),所以(a_i)能和所有(leq)它的(b_j')匹配。

    一个很自然的贪心是将(a)(b')分别排个序,然后一个一个匹配,于是企图维护(b)(a)的一个区间的有序数列,但是这样是做不出来的。

    稍微考虑考虑二分图匹配的霍尔定理(hall定理):二分图存在完美匹配的充要条件是:对于二分图的一部的点集(V),设(V)与二分图另一部的最大匹配数为(f(V)),如果(forall V' subseteq V, f(V') geq |V'|),则二分图的最大匹配为(|V|)

    回到这题来说,显然是越小的(b_j')能匹配的(a_i)越多。将(b')排个序,假设(b_j')能匹配的(a_i)数量为(f(j)),那么只要(forall j. f(j) geq m - j + 1),就存在一个大小为(m)的完美匹配了。

    于是考虑维护(f(j) + j - m - 1),加入一个数和删除一个数都对应着一个区间(+-1)的操作,而询问操作对应着全局最小值是否(geq 0)

    code

    CF300D Painting Square

    怎么只有3个测试点啊……一点都不解压。

    (f(n, k))表示((n, k))时候的答案,写出递推式:

    [f(n, k) = left{egin{matrix} 1 & (k = 0) \ 0 & (n = 1 or n mod 2 = 0) \ sum_{a_1 + a_2 + a_3 + a_4 = k - 1} prod f(frac{n - 1}{2}, a_i) & (k > 0, n mod 2 = 1) end{matrix} ight. ]

    仔细看看这个式子,发现(f(n, k))的取值和(n)没什么特别大的关系,基本上与(n)的奇偶性有关。

    准确来讲是这样的,定义(h(n))表示(n)的“阶数”,定义(h(1) = h(2k) = 0);而对于所有的奇数,定义(h(n) = h(frac{n - 1}{2}) + 1)。也就是说,(h)的值越大,正整数(n)“奇”的程度越强,而(f(n, ?))的取值仅与(h(n))有关。

    发现(h(n))增长的速度很慢,接近一个指数级,可以取一个(maxh = 32)

    于是改写一下上面的递推式,预处理所有答案,然后(O(1))回答。第二步那个生成函数的四次方可以用朴素的dp解决。

    code

    CF200A Cinema

    这个题,就感觉蛮神奇的。

    第一感觉是暴力的复杂度挺对的,因为所有的不能用的格子都是之前涂黑的,第(k)次操作至多有(k)个黑格子,也就是说如果我们能找到一种方法将前(k)次操作产生的“势能函数”按照一个可承受的限度上升的话,复杂度就是均摊正确的。

    然后不会了,再次痛苦面具。

    题解是这样的:对于每一个询问((x, y)),从小到大枚举(|x_0 - x|),按行贪心地去找这一行最好的点是((x_0, y_0)),边做边维护当前答案,如果(|x_0 - x| > res)( ext{break});对于一个修改((x, y))维护第(x)行查询纵坐标为(y)的时候行上最优的纵坐标(y_0),容易发现可以用并查集维护,维护左边最好的答案、右边最好的答案就可以了。

    然后这样子复杂度不超过(O(qsqrt{q})),还有个并查集的复杂度。因为前(q)个操作至多涂满面积为(q)个正方形,这样的正方形边长不超过(sqrt{q})

    code

    代码意外地好写。

    CF95E Lucky Country

    思路很简单。

    首先做出已有的连通块大小,然后考虑背包,设(f(i))表示表示出(i)最多需要(f(i))个数,最暴力的做法是一个一个数往里加入,这样的复杂度肯定不对,但是我们可以合并一下相同的数,简单地跑一个二进制拆分,这样复杂度就正确了。

    只会证复杂度不超过(O(nsqrt{n}log n)),按照(sqrt{n})为界将物品分成容量大的物品和容量小的物品,容量大的物品不超过(sqrt{n})个,这一部分可以(O(nsqrt{n}))暴力跑;容量小的物品的权值种类不超过(sqrt{n})种,处理每一种的复杂度是(O(nlog n))。所以总复杂度不超过(O(nsqrt{n}log n))

    code

    多久没写过多重背包了,已经写不对了,上一次有印象写二进制拆分还是在高一的暑假。

    CF254D Rats

    没有任何神奇的做法,深深感觉到自己的菜……

    首先随便找个老鼠搜一下,这样可能经过的点是(O(d^2))级别的,第一个点至少要在这个集合中;然后枚举集合中的点染色一下,再找一个没有被覆盖到的老鼠再搜一下,这样子两个点所属的集合就分别做出来了,然后暴力检验就可以了。

    时间不超过(O(d^6)),有个不小的常数。

    code

    CF319C Kalila and Dimna in the Logging Industry

    显然每个状态之和当前最大的被砍掉的树的位置有关,考虑dp,设(f(i))表示当前砍掉的最大的树为(i)的最小代价,

    [f(i) = min_{j < i}(f(j) + sum_{k = j + 1}^{i - 1}a_kb_n + a_ib_j), f(1) = 0 ]

    仔细看看,因为(b_n = 0),所以中间那个东西为(0),所以

    [f(i) = f(j) + a_ib_j ]

    显然是个斜率优化。

    code

    复习一下斜率优化:将要求的东西放在(y = kx + b)的截距,也就是(b)的位置,

    [f(j) = (-a_i)b_j + f(i) ]

    相当于平面上有若干个((b_j, f(j)))的点,每次画一条斜率为(-a_i)的线去截,求截距的最大值。此题保证斜率单调,可以使用单调队列维护下凸壳。

    CF39C Moon Craters

    总体上和这道题差不多。

    离散化之后线段长度是(O(n))的,设(f(l, r))表示([l, r])区间内的最大值,有转移

    [f(l, r) = max(f(l, p) + f(p, r) + [ ext{exist}(l, r)]) ]

    还有转移

    [f(l, r) = f(l, r - 1) ]

    直接暴力做是(O(n^3))的,考虑只枚举左端点在(l)的圆,这样的均摊复杂度为(O(n^2))

    我的做法是按照线段长短单独处理每个圆内的可行答案,也可以dp,不太会分析复杂度,但是能过。

    code

    输出方案有一点点麻烦。

    CF123D String

    比较简单。

    后缀数组求个( ext{height}),把所有长度为(L)的串一起考虑,发现一个串的在后缀后出现的位置是连续的一段,连续的条件是每一个( ext{ht})(geq L),于是从大到小枚举(L),不断向序列中加入新的位置,然后用并查集维护一下每个段的答案就可以了。

    code

    CF311B Cats Transport

    怎么把dp方程写反了啊。

    设出发时间为(t_0),那么对于一个(i)能产生贡献的条件是(t_0 + sum d geq t_i),设(b_i = t_i - sum d),将(b)从小到大排序之后,一个(t_0)取走的将会是(b)的连续一段区间。

    于是就可以( ext{dp})了,设(f(i, j))表示第(i)个人把([1, j])的猫猫全部带走的最小代价,有转移

    [f(i, j) = min(f(i - 1, j) + (j - k)b_j - (sum_j - sum_k)) (k < j) ]

    整理一下,

    [(f(i, j) + sum_j - jb_j) +kb_j = f(i - 1, k) + sum_k ]

    左边的那个括号中只有(f(i, j))是要求的,右边只与(k)有关,这很斜率优化。

    相当于向平面上插入(j - 1)个点((k, f(i - 1, k) + sum_k)),然后拿一条斜率为(b_j)的线去截,求截距的最小值。

    由于斜率不降,用单调队列维护下凸壳即可。

    code

    CF176E Archaeology

    可能算是半个结论题?

    假设这些点按树的( ext{dfs})序从小到大拍好是(p_1, p_2, cdots, p_k),那么这些点的导出子树的权值就是(frac{1}{2}( ext{dis}(p_1, p_2) + ext{dis}(p_2, p_3)) + cdots + ext{dis}(p_{k - 1}, p_k) + ext{dis}(p_k, p_1)))

    仔细想想,当只有两个点的时候导出子树就是一条链,而三个点的时候导出子树是三条链的权值总和(/2),相当于这些点按顺序走了一个环,然后每条边都被算了两次。

    于是用( ext{set})随便维护一下就好了。

    code

    CF149D Coloring Brackets

    可以算是一个经验包。

    一个合法的括号序列一定可以写成(A + B)的形式,其中(A)是一个非空的合法括号序列,(B)是一个可以为空的括号序列,(A)的首尾两个括号一定匹配。所以对于一个括号序列我们可以找到第一个(A)进行转移。

    (f(l, r, 0/1/2, 0/1/2))表示合法括号序列([l, r])左边/右边无限制/不能填红色/不能填蓝色的方案数,转移可以预处理出来,状态数为(O(n^2)),有个(9)的常数,转移显然。

    要注意每一对括号都必须染色,不能两个都不染色。

    状态数为(O(n^2)),转移是(O(1))的,常数很大。

    code

    CF293E Close Vertices

    经验包( imes 2)

    直接(3log)在点分里面套一个树状数组,就过了。

    感觉题解是和( ext{dsu on tree})差不多的思想,仔细看了看好像是两维线段树合并,害怕。

    code

    73D FreeDiv

    简单图论。

    考虑这个(k)的限制,发现当(k = 1)的时候限制很强,不妨单独讨论(k = 1)的边界情况,容易发现当(k = 1)时每个连通块最多往外连一条边,所以最后的答案就是把图连成只有两个连通块的最小边数。

    接下来考虑(k geq 2),这时候需要考虑连通块能连出去的边数受到( ext{siz})的限制,特别是( ext{siz} = 1)的连通块,这些连通块只能放在叶子的位置上,其他连通块构成了这棵树挖掉叶子之后的“中间部分”,设有(leaf)个叶子,中间部分的大小为(cnt),考虑中间部分能连的边数,一共为(sum_{x} min(k, ext{siz}_x)),将中间部分连通需要消耗(2(cnt - 1))条边,剩下的度数即为能连接叶子的度数。换句话说,若满足

    [sum_{x} min(k, ext{siz}_x) - 2(cnt - 1) geq leaf ]

    则不需要添加任何一条边;否则考虑怎么加边。

    发现加边的时候连接两个中间结点不赚,因为中间结点的( ext{siz})变大之后容易受到(k)的限制,同理,连接一个叶子和一个中间结点也是不赚的,所以连接两个叶子是最赚的。

    连接了一条边之后,左边那个东西不变(相当于一个( ext{siz})为2的连通块连到了中间,消耗一个位置贡献一个位置),右边减2,因为这题中叶子的数量不超过(n),直接暴力检验就可以了。

    code

  • 相关阅读:
    Python列表推导式,字典推导式,元组推导式
    python装饰器@,内嵌函数,闭包
    7-route命令
    6-mv命令
    5-ln链接命令
    4-linux建立用户用户组以及新用户的赋权
    3-gzip压缩解压命令
    2-date命令
    1-cp命令
    UIViewContentMode的各种效果
  • 原文地址:https://www.cnblogs.com/CzxingcHen/p/15225799.html
Copyright © 2020-2023  润新知