杭州集训 Day 3
课前
早饭 htdlz 帮忙买的,一碗粥和三个不知名的糕点,粥并不好喝,但是糕点好吃。
早上到了机房把这儿的小破电脑换成了自己的笔记本,屏幕大一点舒服一些。hs_black 走了,今天换 syksykccc 来讲,syk 开朗幽默的多,上课和机房这群很有话题。而且他甚至把他讲的每个题对应的代码打了,然后课后发了。
课前他自我介绍说:“我是高一拿的 Ag,后来觉的 OI 比 whk 难然后就 AFO 了,最后裸分考的北大。”
syk 还发表了一些逆天言论,如 “破链为环” 解决区间 dp 问题
笔记
P2051
设 \(f[i][j][k]\) 表示从左上角一直到坐标 \((i,j)\) 并且用了 \(1\)~\(k\) 个象棋的方案数
\(n\) 的数据范围很小,可以 \(n^3\) 暴力遍历
\(v[i][j]\) 标记棋子
但是转移并不好写,考虑找性质,发现对于每行每列炮的个数不会超过两个
所以将第二,三维优化
\(j\) 表示 有 \(j\) 个列是 \(1\) 个炮,\(k\) 表示有 \(k\) 个列放 \(2\) 个炮,剩下的放 \(0\) 个炮
转移的时候就考虑在当前行放 \(0 / 1 / 2\) 个炮,以及这些炮放在了之前有几个炮的列上
现在可以暴力转移,复杂度三次方级,可以通过
P1437
将原图形旋转变成左下角为直角的等腰直角三角形形式,那么想要拿走一个砖,就必须要拿走左边那个和左上角那个,考虑有依赖的背包 dp
显然的,可以将当前砖和左边那个和左上角那个绑定,他们的分值和为质量,个数为体积,然后背包
但是一定会有重复绑定导致计算错误,所以要考虑找特殊性质,发现第 \(i\) 行被打掉的一定是一个前缀,用 \(f(i, j, k)\) 表示当前考虑到第 \(i\) 行,这一行的轮廓线转折点在 \(j\),已经打了 \(k\) 个砖头的最大收益。
复杂度 \(O(n^4)\)
P6189
显然可以平方级别的背包 dp,但是显然过不了。但是对于背包 dp 它也有两种不同的状态设计,
\(f(i, j)\) 表示把 \(i\) 拆成 \(≤j\) 的数的方案。\(g(i, j)\) 表示把 \(i\) 拆成 \(j\) 个数的方案。这个直接递推转移是非常好实现的。
之后根号分治,对于小于等于 \(\sqrt n\) 的数据计算 \(f\),剩下的计算 \(g\)(这里数据指的是 dp 第二维)
复杂度 \(O(n \sqrt n)\)
之后考虑如何使用 \(f,g\) 更新计算答案
\(M\) 指的是根号分界
P7962
将答案式子展开化简后为 \(n \sum_{i=1}^na_i^2 - (\sum_{i=1}^na_i)^2\)
对于一次操作,原来相邻两个数的差为 \(a_i - a_{i-1}\) 变为了 \(a_{i+1} - a_i\)。即一次操作是在交换差分数组的相邻两项,
可以任意交换差分数组得到一个新的差分排列。为了使所有数字趋于平均值,差分数组应当是单谷序列,微扰法可以证明。将差分数组 \(b\) 从小到大排序,然后依次考虑每一项,要么插在当前最左边,要么插在当前最右边。
令 \(a_0 = 0\),设 \(f(i,s)\) 表示考虑了前 \(i\) 个数,当前 \(∑a_i = s\)
的情况下,最小的 \(\sum a_i^2\) 是多少。
-
\(f(i,s+\sum_{j=1}^{i}b_j) = f(i-1,s)+(\sum_{j=1}^{i}b_j)^2\),放在右边
-
\(f(i,b_i*i+s)=f(i-1,s)+i*b_i^2+2*b_i*s\) ,放在右边
直接 dp 复杂度为 \(O(n^2a)\)
注意到差分数组中非 \(0\) 项至多 \(O(a)\) 个,而对于 \(b_i = 0\) 毕竟排在最前面,无论放左放右都是一样的,所以直接铺开就行了,于是时间复杂度就能做到 \(O(na^2)\)
P6764
计算出 \(P_i = 0/1\) 表示能否从 \(i\) 开始进行一次粉刷,之后的就是很平凡的贪心区间覆盖问题。
假设现在 \(|F_c| ≤ 1\),那么每个墙能指派的承包商是固定的。用 \(f(i)\) 表示从第 \(i\) 段墙开始最远能刷到哪里。
用 \(f(i, j)\) 表示从第 \(i\) 段墙开始,让承包商 \(j\) 来粉刷,最远能刷到哪里。可以 \(O(nm)\) 逆序递推。初始化 \(0xcf\),对于 \(j ∈ F_{c_i} ∧ (i = N − 1 ∨ (j + 1) \mod m ∉ F_{c_i}+1\) 的情况,\(f(i,j) = i\)。剩下的 \(j ∈ F_{c_i}\) 的情况 \(f(i + 1,(j + 1) \mod m)\)
然后还有一些优化的 trick 卡一下就过了
P5289
用 \(f(x, y)\) 这样的状态去做背包,表示考虑到当前学校的时候,红阵营有 \(x\) 人,\(A\) 派系有 \(y\) 个人的方案数。复杂度是 \(O(nM^2)\)
假如 \(k = 0\) 发现可以先计算阵营的分配方案数,再计算派系的方案数,然后把两个乘起来即可。这两个都是可以一维背包实现的,复杂度 \(O(nM+cM)\)
对没有限制的学校用分别 \(dp\) 的方式,对于有限制的学校,因为不超过 \(k\) 个,所以用二维 \(dp\) 的方式再合并一下答案,总复杂度 \(O((n + c)M+ k^2sM)\)
P4302
用 \(f(l,r)\) 表示 \(S[l · · ·r]\) 的答案。
-
第一条:\(f(l,r) ← (r − l + 1)\)
-
第三条:\(f(l,r) ← f(l, k) + f(k + 1,r)\)
-
第二条:枚举循环节长度 \(k\),
\(f(l,r) ← f(l, l + k − 1) + 2 + digits((r-l+1)/k)\)
枚举 \(k\) 是 \(r − l + 1\) 的因子的去检查,调和级数可得复杂度 \(O(n^3 \log n)\)
P6879
时间值域太大了,不好设计进状态,但是答案很小,考虑交换定义域和值域设计 dp 状态。
\(f(l,r, b_{lr}, k)\) 表示当前考虑了 \([l,r]\) 这一段的所有打卡点,
\(b_{lr}\) 表示终止时站在 \(l\) 还是 \(r\),真正成功打卡了 \(k\) 个点时,最小花费的时间。用每一个已经求出的 \(f(l,r, b_{lr}, k)\) 的状态去更新 \(f(l − 1,r, *, *)\) 和 \(f(l,r + 1, *, *)\)。\(k\) 是否要增加可以根据两个 \(b\) 的值很方便地计算。
那么初始时把所有的状态都赋值为无穷,如果一个状态的最小时间不是无穷就说明有意义,用它去刷表。最后要查看有意义的状态的 \(k\) 的最大值。
复杂度 \(O(n^3)\)
P1864
不能修改数据值,所以这棵树的中序遍历固定。另一点是,由于可以修改为任意实数,实数是可以无限接近但是不相等的,所以完全可以理解为,允许权重相同,并且权重相同的点可以以任何你想要的顺序排序。即允许假定权重是整数来考虑这个问题。用 \(f(l,r, k)\) 表示把 \([l,r]\) 建树并且保证子树内所有点的权重都 \(≥ k\) 的时候,最小化的答案。\(f(l,r, k)\) 通过枚举根节点 \(t\),分别考虑不需要 /需要修改,利用下两式来更新
其中 \(∑freq_i\) 表示深度 \(+1\) 之后代价的增量。总复杂度为 \(O(n^4)\)
P7605
设 \(f(l,r)\) 为答案
转移 \(f(l,r)\) 考虑第一步选择了左边还是右边,不妨设左边。
如果最终都没消去,那么就是 \(f(l + 1,r) + (1, 1)\)
如果最终利用 \(c_k\) 消去了,考虑一下 \(c_k\) 是从左端取出的还是
从右端取出的。
不妨设是右端取出的,那么显然 \([k + 1,r]\) 要先被删除,考虑
枚举 \(j\) 表示 \([l + 1, j]\) 辅助了这一段的删除,那么转化后的子
区间就是 \([j + 1, k − 1]\)
用 \(\max\{f(j + 1, k − 1),2, 1 + g(l + 1, j, k + 1,r)\}\) 来表示这个转移
其中 \(g(l_1,r_1, l_2,r_2)\) 表示完全消除 \([l_1,r_1] ∪ [l_2,r_2]\) 所需要的栈的大小。当然也有可能为无穷,此时对应的状态不能用来转移
。
关于 \(g\) 的转移,考虑是先取出 \(l_1\) 还是 \(r_2\)。
比如是 \(l_1\),考虑它和哪个位置配对,比如是和 \(c_i\),其中 \(i ∈ [l_2,r_2]\)。再枚举左侧辅助消除 \([i + 1,r_2]\) 的区间是 \([l_1 + 1, j]\),那么转移就可以表示为 \(\max\{g(l_1 + 1, j, i + 1, r_2) + 1, g(j + 1,r_1, l_2, i-1)\}\)
理论复杂度 \(O(n^6)\) ,实际上可以快速判断一些 \(g\) 的值为无穷,比如区间长度和是否为偶数,区间内的数是否两两配对(可以哈希之后求异或
和)。这样常数很小。可以用记忆化搜索实现。
P7077
如果没有第 \(2\) 类,考虑计算每个第 \(1\) 类函数的实际被调用次数。
第 \(2\) 类函数可以看作,对原始数据的乘法 + 将之前的第 \(1\) 类函数调用 \(V_i\) 次。
建立一个 \(DAG\),每个第 \(3\) 类函数(可以把 \(f_1, · · · , f_Q\) 也建模成一个第 \(3\) 类函数)向它所调用的所有函数依次连边。
首先考虑计算原始数据的贡献,这个只需要一次 dp 求出每个函数调用造成的全局乘积大小,然后就可以方便的计算总倍率。
逆序第 \(3\) 类函数序列。动态维护一个乘积 \(prod\) 表示当前子函数需要执行多少次。每遇到一个子函数,就在它对应的节点上打 \(+prod\) 标记,然后再更新 \(prod\)。这样便可以拓扑排序后利用 dp 求出每个函数需要调用的次数,累加贡献。
复杂度 \(O(n + \sum C_i)\)
P4577
每个节点开一个 multiset
\(f_u\),从大到小的第 \(x\) 个数,表示当答案为 \(x\) 时的最大转移权值。
首先不考虑 \(u\) 的话,\(f_u =∪f_v\)
考虑 \(u\),那么把 \(w_u\) 插入 \(f_u\),然后除非 \(w_u\) 是 \(f_u\) 中最小的,否则把 \(w_u\) 的前一个 iterator
删除。合并 \(f\) 时可以使用启发式合并,复杂度 \(O(n \log^2 n)\),如果使用权值线段树维护可以做到单 \(\log\)
P5024
设 \(f(u, 0/1)\) 表示 \(u\) 的子树,\(u\) 不选 / 选,从“转移模式”来
看,除了 \(a, b\),其他的点依然在做相同的操作。
首先一次 \(dp\) 求出前文提到的 \(f(u, 0/1)\) 数组,现在考虑把 \(f\) 倍增化。用 \(g(u, k, 0/1, 0/1)\) 表示对于 \(u\),它选不选,它的 \(2^k\) 级父亲 \(fa\) 选不选,则 \(tree(fa) − tree(u)\) 的最优代价。初始化为
倍增转移时枚举 \(2^{k−1}\) 级父亲的状态
现在分两种情况讨论询问。如果 \(a, b\) 是祖先-后代关系,那么,先用 \(b\) 的 \(f\) 去初始化状态 \(res_0,res_1\),分别表示当前的根节点是不选/选的最小代价。然后用 \(g\) 去倍增到 \(a\),再把 \(res_x\) 赋值为 \(0\),最后一路倍增到根节点即可。如果不是的话,同理,分别从 \(a, b\) 用上面的操作倍增到 LCA 下方的节点,然后在 LCA 处暴力合并答案,再倍增到根节点即可。复杂度只有一个 \(\log\)
课后
绝区零常驻池第一个常五是狼哥,第二个十连出了苍角,抽那个小不点不知道是什么的东西上来十连出了一个金,后来换成鲨什么蓝色的那只十连又出了。
暗示我抽鲨鱼妹
但是我 50 抽了还没出,运气太差
傻鸟米哈游不如洛谷好玩