集训游记 7.15-7.17 基础算法/动态规划
Day1
LOJ#6490. 「XXOI 2018」暑假时在做什么?有没有空?可以来学物理吗?
考虑 CDQ 分治.设当前分治区间为 \([l,r]\),\(\displaystyle mid=\frac{l}{r}\).考虑跨 \(mid\) 区间的贡献.
先更新 \([mid+1,r]\) 中的点的答案.枚举区间右端点,左端点在 \([l,mid]\) 中的部分形成一个区间.做一个前缀和后可以转化为区间 RMQ 问题.然后区间 \([l, mid]\) 中的点的答案同理.枚举区间左端点,然后右端点在 \([mid+1, r]\) 中的部分形成一个区间.
Buy Low Sell High
反悔贪心.对于某一天 \(i\),假设在这天卖出一只股票,肯定要在前面的某一天买进一只股票.假设买进天为 \(j\),则利润为 \(P_i-P_j\).按天数顺序扫描,用小根堆维护之前每天股票的价格.若选择当前天卖出股票,肯定优先选择股票价格最小的天数.加入反悔机制:若当前天选择了卖出股票,将 \(P_i-P_j\) 放入小根堆.
P5290 [十二省联考 2019] 春节十二响
7.15Test T4
考虑构造 \(p\) 进制线性基.由于 \(p\) 是质数,故任意一个非 \(0\) 的数在模 \(p\) 意义下的 \(k\) 倍可以张成剩余系中的所有数.故我们将所有数按行依次写开构造矩阵.将前面的位数保留成为自由元.然后贪心地使它们最大即可.
具体地,考虑插入一个数.跟异或线性基相同的,从高位开始枚举,若碰到不存在数字,并且自己哪一位非 \(0\) 的位数,将自己插入到线性基的那一位.否则将它的那一位消成 \(0\).
Day2
主要讲的是 DP.然后题补的不多.简单地记一下.
然后相对来说比较简单的题目就记一下大体思路.难题还是尽量给出实现.
做题记录以例题为主,之后会去补作业.
序列 DP
序列 DP 的题目.由于序列的拓扑结构相对简单,所以题目本身往往相对诡异.写题时容易无法下手.需要斟酌状态设计、转移顺序和优化方式.有时可以思考重排序列使得有贡献的转移集中在某一区间.
P7154 [USACO20DEC] Sleeping Cows P
由于保证了奶牛和牛棚大小互不相同,我们可以考虑把奶牛和牛棚放在一起排序:每个元素只能和前面的元素匹配,这个性质是有利于设计 DP 状态的.
先不考虑极大匹配的限制.记 \(F_{i,j}\) 表示前 \(i\) 个元素有 \(j\) 个奶牛未匹配(待匹配).按奶牛和牛棚分类转移即可.
再来考虑极大匹配的限制.那么每个奶牛就有两种选择:
- 选择匹配,需要等待后面的某一个牛棚.
- 选择不匹配,不允许后面有闲置的牛棚.
虽然一个奶牛选择是否匹配影响了后面的牛棚的抉择状态.但我们不妨在遍历到奶牛的时候就 提前决定 其是否选择匹配.为防止后效性,肯定是需要重新设计 DP 数组的.
记 \(F_{i,j,0/1}\) 表示前 \(i\) 个元素,有 \(j\) 个 待匹配的 奶牛,是否存在选择不匹配的奶牛.这样的好处是遍历到牛棚时,容易检查出对于一种状态,它是否 必须和前面的某一个待匹配奶牛匹配.
有转移方程:
\(i\) 是牛棚:
\(i\) 是奶牛:
P8863 「KDOI-03」构造数组
操作次数显然是固定的 \(m=\displaystyle \frac{\sum b_i}{2}\).
那么我们将每个操作时的数组看成一列,会形成一个大小为 \(n\times m\) 的网格.然后每一列可以选择两个点 \(+1\).要求每一行恰有 \(b_i\) 个 \(+1\) 的方案数.
设计 DP 状态,记 \(f_{i,j}\) 表示前 \(i\) 行有 \(j\) 列已经添入了两个 \(+1\).这样的好处就是添入一个 \(+1\) 和没有添入的列数都可以算出来.
转移枚举当前行有多少个 \(+1\) 填在了之前有一个 \(+1\) 的列上即可.
有转移方程:
其中 \(m_{i-1,1},m_{i-1,0}\) 分别表示前 \(i-1\) 行选择了 \(j\) 个有两个 \(+1\) 的列,剩下的中有一个 \(+1\) 和没有的列的个数.
CF1363F Rotating Substrings
非常神仙的线性 DP.循环位移一位很丑,我们可以想成:把某一位的字符扔到序列的前面某一位去.就有一种特别神奇的,从后往前 DP 的方法.
记 \(F_{i,j}\) 表示考虑 \(S\) 的后缀 \(i\) 通过将后缀中的字符扔到后缀中,或者挂起后缀中的某些字符,最终达到和 \(T\) 的后缀 \(j\) 匹配的最小挂起字符个数是多少.
这里 “挂起” 的意思是将某字符拎起来,但还不决定把它扔到哪.
对 \(T_j\) 的匹配方式分类可以得到:
- \(S_i 和 T_j\) 匹配,有
- \(S_i\) 挂起,让后面的 \(S\) 匹配,有
- \(S_i\) 去匹配后面的 \(T\),把后面 \(S\) 中挂起的等于 \(T_j\) 的字符扔到这里来和 \(T_j\) 匹配.
这时要有 \(S\) 的后缀 \(i\) 中 \(T_j\) 字符的数量大于等于 \(T\) 中 \(j\) 字符的数量.
CF1188C Array Beauty
将 \(a\) 数组排好序.
发现 \(\min(|b_i-b_j|)=x\) 的子序列数量是难求的,但 \(\min(|b_i-b_j|) \ge x\) 的子序列数量容易求出的.记 \(num_x=\min(|b_i-b_j|) \ge x 的子序列数量\).有
这是我们已经可以做了.但其实它有更美观的形式.设 \(num'_x=\min(|b_i-b_j|) = x 的子序列数量\):
P7985 [USACO21DEC] Paired Up P
存在性质:交叉匹配一定不更优于不交叉的匹配.因为这样可以减小两对匹配的距离绝对值.或者这样说:若存在若干对匹配相交,肯定有一种合法的调整方案使得它们不相交.
考虑 \(T=1\).最小化是不需要考虑极大匹配限制的.因为一组非极大匹配肯定不能成为最优解:将其变成极大匹配答案肯定更小.我们可以记 \(F_{i,j}\) 表示前 \(i\) 头荷斯坦牛和前 \(j\) 头更赛牛的答案.有转移:
再考虑 \(T=2\).这个就稍微有点麻烦了.将一头牛变成未匹配的时候,要保证它和前面未匹配的牛不能形成匹配.于是考虑改变状态.
记 \(F_{i,j,0/1}\) 表示处理完前 \(i\) 头荷斯坦牛,前 \(j\) 头更赛牛,然后下一头 荷斯坦牛/更赛牛 可以计入贡献的答案.
尝试理解一下这个抽象的状态:就是说下一头 荷斯坦牛/更赛牛 和前面的所有 更赛牛/荷斯坦牛 全部无法匹配.转移时分类讨论:
- 保留待贡献的牛的种类.
- 尝试改变待贡献的牛的种类.
每次找出某一头 荷斯坦奶牛/更赛牛 位置之后的,第一头不能与它匹配的 更赛牛/荷斯坦牛 的 前一个位置,并尝试转移到它那里来.
树形 DP
有点遗憾没讲到换根 DP,反倒是图论讲缩点的时候谈了一下.
CF1778F Maximizing Root
首先有结论,自底而上操作的顺序肯定更优.这符合我们自底而上的 DP 顺序.
最大化 \(a_1\),其实是最大化 \(a_1\) 最终乘上的倍数.如果可以乘上倍数 \(d\),必须满足 \(d\) 是 \(a_1\) 子树的因数.正常的想法,应该设计状态考察耗费一定代价能获得的最优因数.但发现子树中最大的因数,从全局来考察不一定在最优.那么我们必须把每一个因数都纳入考量.使用一个改变状态的小技巧:把 DP 的答案纳入 DP 状态中,DP 答案的最小花费,而不是某一花费上的最优答案.而花费的优劣是容易抉择的.
具体地,我们设 \(F_{u,i}\) 表示满足 \(u\) 子树存在公因数 \(i\),子树中需要操作的最小次数.
对于不操作 \(u\) 的情况,\(F_{u,i}\) 是将子树的 \(F_{v,i}\) 逐点相加.对于操作 \(u\) 的情况,即 \(F_{u,i}+1 \rightarrow F_{u, i*i}\).
考虑缩小状态数,发现使 \(a_1\) 可以进行操作,必须使得子树存在因数是 \(a_1\) 的因数.而 \(a_1\) 的因数在 \(1e5\) 的范围内至多只有 \(128\) 个.
P8352 [SDOI/SXOI2022] 小 N 的独立集
相当于在树上最大独立集的外壳假象下的计数.容易想到状态 \(F_{u,i,j}\) 表示 \(u\) 子树,勒令 \(a_u\) 不选和勒令 \(a_u\) 选的答案.转移时考虑添加一颗新子树.
这时有技巧 DP 套 DP!就是说状态合并时,转移的新状态需要由原来状态通过某些决策 DP 出来.考察 \(F_{u,i,j},F_{v,i',j'}\) 合并.指向的状态为 \(F_{u,max(i+i',i+j'),j+j'}\).
非常优美非常和谐!状态数爆炸!
继续寻找性质:发现勒令不选的答案不会比勒令选的答案小很多.事实上,勒令选的答案去掉 \(a_u\),就是一种合法的勒令不选的答案.我们新设计一种树上最大独立集的 DP 状态:\(G_{u,0}\) 表示子树 \(u\) 勒令不选 \(u\) 的答案,\(G_{u,1}\) 表示子树 \(u\) 不管是否选择 \(u\) 的答案.有 \(G_{u,0} \in [G_{u,1}-a_u, G_{u,1}]\).我们只需要记录一个值,和两个值的差即可!
再来简要分析一下树上背包的时间复杂度:
for(int v:e[u]){
dfs(v, u);
for(int i=1; i<=sz[u]; i++){
for(int j=1; j<=sz[v]; j++){
...
}
}
sz[u]+=sz[v];
}
考虑一份这样的树上背包,其实在实现一个这样的过程:
枚举子树 \(v\) 和之前子树的贡献,将子树 \(v\) 并入之前的子树.两层循环实际在枚举 \(lca\) 在 \(u\) 处的点对.每次枚举的 \(i,j\) 可以一一对应到树上的点对上,故总时间复杂度为 \(O(n^2)\).
而对于此题,由于还要枚举额外的增量数组.综时间复杂度为 \(O(n^2m^4)\).
Swap and Maximum Block
\(\red{还没补.}\)
根据序列建出 trie 树.
发现交换每两个相邻的 \(2^k\) 区间等价于在 trie 树上交换所有子树宽度为 \(2^{k+1}\) 的节点的两个儿子.
对于 \(k\) 级子树 \(u\),其子树的最大深度为 \(n-k\).子树内每层是否交换的数量总和为 \(2^{n-k}\).而 \(k\) 级子树一共约有 \(2^k\) 个.故 \(k\) 级的总状态数为 \(2^k \times 2^{n-k}=2^n\).所以 \(n\) 层的状态一共有 \(n2^n\) 种.
状态压缩 DP
有一道 Codeforce 训练场的题,但主要是乱用 bitset 瞎搞.有时间去补一下.
P7142 [THUPC2021 初赛] 密集子图
主要难在刻画最短路.我们把原图最短路小于等于 \(i\) 的部分,集合 \(S\) 划分成两部分:最短路等于 \(i\) 的部分 \(S_1\) 和 小于 \(i\) 的部分 \(S-S_1\).考虑加入最短路为 \(i+1\) 的部分 \(S'\).发现对于 \(u\rightarrow v (u\in S-S_1,v\in S')\),连边必须为白色.对于 \(u \rightarrow v(u\in S_1,v\in S')\),连边对于每一个 \(v\) 至少存在一个为黑色.然后就可以转移了.
记 \(F_{i,S_1,S_2}\) 表示考虑了所有最短路 \(\le i\) 的点,其点集为 \(S_1\),其中最短路恰好为 \(i\) 的点集为 \(S_2\),发生这种情况的概率.
存在转移
然后我们预处理 \(P,P'\) 即可.
P7519 [省选联考 2021 A/B 卷] 滚榜
又是一道代价提前计算的好题!
发现让我计算可能排列的个数:一种排列只能算作一次.
考虑状压队伍的集合,然后按揭榜的顺序 DP 是较容易的.由于 \(b\) 的取值不确定又影响答案,必须要计入 \(DP\) 的考量范围,我们考虑对于任意一种排列对应到唯一的一种 \(b\) 的数列上,这样就能保证不算重.
但同时不能漏掉可能的排列:要保证任意一种可能合法点排列在我们的映射方案上都是合法的!
贪心.顺序考虑揭榜的每一个队伍,在保证其能达到当前榜首的前提下,确保其 \(b\) 尽量小.这样的构造方法就完美符合了我们的要求.
区间 DP
P4563 [JXOI2018] 守卫
突破口非常奇怪.但又很合理.
考虑一个性质:守卫只能向左边看.那么对于一个区间 \([l,r]\),要使得在这个区间中放置守卫使得所有地方被看见,区间的右端点 \(r\) 一定需要放置守卫.那么设 \(r\) 在区间中能看见的最左的地方为 \(p\).可以证明:\(\forall i\in[p,r]\),不存在一个地方 \(j\in[l,p-1]\),使得 \(i\) 可以看到 \(j\).具体来说,在 \((p,r)\) 之中的点一定存在于 \(p,r\) 连线的下方.若某一点可以看到 \(p\) 左边的点,那么 \(r\) 一定也能看到.如果画一张图就会非常清楚.
那么这个区间就被我们划分成了两部分:\([l,p-1],[p,r]\).这两个部分互相没有关联.设 \(F_{l,r}\) 表示看守 \({l,r}\) 需要的最多人数.那么我们就有了转移:
还有一个细节.那就是当 \(p=l\) 的时候,这个转移就不成立了.那我们再考虑第二靠左的,能被 \(r\) 看见的地方即可.
CF1178F2 Long Colorful Strip
这个区间 DP 思路还比较自然.
首先想到可以离散化.一次划分顶多产生两个颜色不同的分解点.如果离散化之后的数组长度 \(> 2n\),可以直接判断无解.
考虑设计 DP 状态.设 \(F_{l,r}\) 表示把区间 \([l,r]\) 按颜色顺序覆盖完的方案数有多少种.
转移时可以确定最小颜色围出的区间肯定要覆盖最小颜色.然后我们枚举覆盖的区间向左右延伸多少即可.这样会存在许多非法的状态.设最小颜色围出的区间是 \([l',r']\).考虑存在另一种颜色在 \(l'\) 的左边出现并且在 \(r'\) 的右边出现之类的情况,其实是无解的.那我们可以做一个这样的特判:当最小的颜色在数组中的出现位置不完全包含于当前的区间中,我们认为当前区间无解.这样就能规避掉非法的状态了.
P9129 [USACO23FEB] Piling Papers G
另辟蹊径的数位 DP.
首先答案是可差分的.那么求坐落于 \([A,B]\) 之间的答案就相当于求 \(\le B\) 的答案减去 \(\le A-1\) 的答案.那么我们考虑如何求出形如 \(\le T\) 答案.
像常规方式一样记录 \(F_{i,0/1/2}\) 表示考虑已经填入的低 \(i\) 位和 \(T\) 的比较结果是 小于/等于/大于.那么当实现转移 \(\overline {a_i+x}\rightarrow x\) 的时候比较结果是容易更新的,但实现转移 \(\overline {x+a_i}\rightarrow x\) 的时候由于前面确定的位数都要向更高位挪动一格,比较的结果就不能确定了.所以这个状态是不可取的.
同样是 提前计算/提前确定 的思想,我们设 \(F_{l,r,0/1/2}\) 表示填入了在 \([l,r]\) 中的位数,和 \(T\) 的 \([l,r]\) 中比较的结果为 小于/等于/大于 的状态数.那么向左添一个就相当于 \(l-1\),向右填一个就相当于 \(r+1\).同时大小的比较也是很容易确定的.注意到 \(q\) 的数量级是 \(O(n^2)\).我们预处理出所有区间的答案,然后 \(O(1)\) 回答即可.
其它 DP 题目
P7967 [COCI2021-2022#2] Magneti
抱き寄せて欲しい 確かめて欲しい
想要被拥入你怀中 想要确认心意
間違いなど無いんだと 思わせて
让我知道 没有误会了什麼
首先按 \(r\) 从小到大排序.那么新添加一个磁石只需要考虑不吸引别的磁石即可.
设计状态就需要一些奇思妙想.设 \(F_{i,c,l}\) 表示考虑前 \(i\) 个磁石,形成 \(c\) 个联通块,耗费的总长度为 \(l\) 的方案数.
然后考虑添加一块磁石可能的三种变化:
- 独立为一个联通块:
- 和其中的某一联通块合并:
- 和其中两个联通块串起来:
那么最后答案肯定是 \(\sum_i F_{n,1,i}\).

浙公网安备 33010602011771号