算法基础选讲
算法基础选讲
算法设计思想
二分、倍增和分治
聪明的质监员
注意到,随着 \(W\) 增大, \(y=\sum y_j\) 减小,这具有单调性。故我们考虑二分 \(W\) ,然后计算此时的 \(y\) ,更新 \(|s-y|\) 的最小值。若 \(y>s\) ,则需增大 \(W\) ;反之,则减小 \(W\) 。于是现在问题转变为了要计算 \(y=\sum y_j\) ,考虑用前缀和。记 \(Sa=\sum w_j\ge W,Sb=\sum (w_j\ge W)\times v_j\) 。故 \(y=\sum (Sa_{r}-Sa_{l-1})(Sb_r-Sb_{l-1})\) ,时间复杂度为 \(O((n+m)\log w)\) 。
跳石头
考虑二分答案 \(mid\) ,每次跳跃时距离不能小于 \(mid\) ,同时计算至少要移走 \(k\) 块石头。若 \(k<M\) ,则增大 \(mid\) ;反之,则减小 \(mid\) 。为最小化 \(k\) ,有这样的一个贪心策略:设当前石头为 \(x\) ,下一块石头为 \(y\) 。若 \(d_y-d_x<mid\) ,则要移除 \(y\) 。现证明移除 \(x\) 不优于移除 \(y\) :设 \(y\) 后面的下一块石头是 \(z\) ,则若 \(d_z-d_y<mid\) ,且我们移除了 \(x\) ,那么此时还一定需要移除 \(y\) 。但如果我们移除了 \(y\) ,则可能 \(d_z-d_z \ge mid\) ,此时就保证了最小化 \(k\)。
借教室
如果直接暴力是 \(O(nm)\) 的,考虑优化。我们可以对天数建立线段树,维护区间最小值。每次先查询区间 \([s,t]\) 内的最小值 \(Min\) ,若 \(Min\ge d\),则对区间 \([s,t]\) 减去 \(d\) ,时间复杂度为 \(O(m\log n)\) 。
回归本节讨论的内容,我们换一种思路。我们可以考虑前 \(r\) 个订单能否满足,假设答案为 \(k\) ,则 \(r<k\) 时可以满足;反之,则不行。答案满足单调性,可以二分。给定 \(r\) 后,订单的顺序不重要,我们关心的是 \(r\) 个订单后每天剩余多少个教室,如果出现负数则不满足。考虑离线,用差分来做即可。时间复杂度为 \(O((n+m)\log m)\) ,在本题中更为优秀。
开车旅行
首先注意到一个性质:对于一个确定的起点 \(i\) 而言,车移动的路线是固定的,因为这只和城市海拔有关。我们先预处理出离第 \(i\) 个城市最近的城市 \(b_i\) ,次近的城市 \(a_i\) ,那么车的移动路线为 \(i\to a_i\to b_{a_i}\to \cdots\) 。
然后我们考虑如果只能行驶 \(x\) 公里,能行驶到什么地方。如果我们枚举每一步,则最坏可能是 \(O(n)\) 的,无法接受。考虑二分。我们称 \(A,B\) 各开一次为一轮,也就是 \(i\to b_{a_i}\) 。二分交替 \(k\) 轮之后经过的距离,与 \(x\) 进行比较。那么经过 \(k\) 轮后,他们落在了哪里呢?考虑倍增。设 \(f_{i,j}\) 表示从 \(i\) 开始经过 \(2^j\) 轮后到达的点。于是显然有 f[i][j]=f[f[i][j-1]][j-1]
,边界情况为 f[i][0]=b[a[i]]
。设 p[i][j]
表示从 \(i\) 经过 \(2^j\) 轮之后 \(A\) 开过的距离, q[i][j]
表示从 \(i\) 经过 \(2^j\) 轮之后 \(B\) 开过的距离。故 p[i][j]=p[i][j-1]+p[f[i][j-1]][j-1],q[i][j]=q[i][j-1]+q[f[i][j-1]][j-1]
。
那么假设从 \(i\) 出发,倒着枚举 \(j:\log n\sim 0\) ,判断能否开 \(2^j\) 轮,即 \(p_{i,j}+q_{i,j}\le x\) 。如果可以就继续开,并从 \(x\) 中减去这部分距离。如果不能开,则我们还需要判断 \(A\) 能否单独开最后一次。这样判断一次是 \(O(\log n)\) 的。
最后考虑怎么预处理出 \(a_i,b_i\) 。我们可以用 set<pair<int,int>> S
,每次将 <h[i],i>
插入 \(S\) 中。查找的时候只需要找到比 \(h_i\) 大的最近两个数和比 \(h_i\) 小的最近两个数进行判断即可。总的时间复杂度为 \(O((n+m)\log n)\) 。
Nudist Beach
题意:给定一个 \(n\) 个点 \(m\) 条边的无向图。你可以在 \(n\) 个点中选择若干个点构成一个集合 \(S\) ,其中,有 \(k\) 个坏点不能被选择。对于 \(x\in S\) 有一个活跃度 \(v\) 为 \(x\) 在 \(S\) 中的邻居数量除以 \(x\) 的邻居数量。 现在希望你选出的 \(S\) 中,最小的活跃度尽可能大。 \(1\le n,m\le 100000\) 。
考虑二分最小答案 \(mid\) ,判断是否存在这样的 \(S\) 。我们可以先把非坏点都先选进来,此时显然是活跃度最大的情况。若其中某个点的活跃度已经小于 \(mid\) 了,则说明这个点不可用了,因为比如某个点的邻居都是坏点,那么选进来肯定是无作用的。
每移除一个点,就需要对它的邻居重新计算其活跃度。因此我们可以用一个队列 \(Q\) 维护待计算的点。初始时,我们把所有可选的点都放进队列 \(Q\) 中。验证 \(Q\) 中的每一个点,如果不符合要求,那么将其标记为不可选,并将它的邻居都加入到 \(Q\) 中,直到没有点可以删除为止,判断当前可加入 \(S\) 的点是否为空。总的时间复杂度为 \(O(n\log V)\) ,其中 \(V\) 表示二分的次数。
Pair of Numbers
题意:有一个长度为 \(n\) 的正整数序列,你需要找到有序对 \((l,r)\) ,满足: \(1\le l\le r\le n\) ,存在一个 \(j\in[l,r]\) ,使得 \(a_l,a_{l+1},\dots,a_r\) 都可以被 \(a_j\) 整除。要求最大化 \(r-l\) 。 \(1\le n\le300000,1\le a_i\le10^6\) 。
注意到这样一个性质,如果存在满足上述条件的 \(a_j\) ,则 \(\begin{aligned}a_j=\min^r_{i=l}a_i=\gcd^r_{i=l}a_i\end{aligned}\) 。于是问题转化为最大化一个长度为 \(k\) 的区间 \([l,r]\) ,满足 \(\begin{aligned}\min^r_{i=l}a_i=\gcd^r_{i=l}a_i\end{aligned}\) 。用 ST 表快速查询一个区间的最小值和 \(\gcd\) 即可。
Tricky Function
题意:有一个长度为 \(n\) 的整数序列和函数 \(\begin{aligned}f(i,j)=(i-j)^2+(a_{i+1}+a_{i+2}+\cdots+a_j)^2(i<j)\end{aligned}\) ,要求 \(\begin{aligned}\min_{i\neq j}f(i,j)\end{aligned}\) 。 \(2\le n\le 100000,0\le|a_i|\le10000\) 。
我们考虑做这样一次转换:\(\begin{aligned}f(i,j)=(i-j)^2+(S_j-S_{i})^2=(i-j)^2+(S_i-S_{j})^2(i<j)\end{aligned}\) 。注意到 \(\begin{aligned}(\min_{i\neq j}f(i,j))^2\end{aligned}\) 此时就是平面最近点对 \((i,S_i)\) 的距离,这是一道经典的分治题。
我们将这 \(n\) 个点加入一个集合,记为 \(S\) 。按 \(x\) 轴排序,我们选择一条垂线 \(L\) 把集合均匀分成两个集合,分别记为 \(S_L,S_R\) 。如此,我们可以递归地找到左边最近的点对的距离 \(\delta_L\)和右边最近的点对的距离 \(\delta_R\) 。
最后我们要考虑的是横跨 \(L\) 两侧的点对的最近距离。记 \(\delta =\min(\delta_L,\delta_R)\) 。我们称以 \(L\) 为垂直平分线,两侧各 \(\delta\) 个单位的条形区域为长带,那么这个横跨 \(L\) 的点对一定在这个长带内。对于 \(L\) 左侧的一个点 \(p\) ,我们只需要检查在 \(x\) 轴上距离 \(L\) 右侧 \(\delta\) 个单位的和在 \(y\) 轴上距离 \(p\) 上下各 \(\delta\) 个单位的矩形区域。因为矩形内的点满足两两距离都大于 \(\delta\) ,故可以证明该矩形区域内最多有 \(6\) 个点。
所以我们只要把这个长带里的点全部投影到 \(L\) 上。然后对于 \(p\) ,我们找 \(y\) 坐标在它前后的 \(6\) 个点进行计算即可。保险起见可以多计算几个,这并不会影响时间复杂度。这样合并的复杂度是:排序的复杂度+线性枚举。如果用快速排序,则总的时间复杂度为 \(O(n\log^2n)\) ;如果用归并排序,则总的时间复杂度为 \(O(n\log n)\) 。
Mashmokh and Reverse Operation
题意:有一个长度为 \(2^n\) 的正整数序列。有 \(m\) 次操作,每次操作给出一个 \(k\) ,按照 \(2^k\) 的长度分组每一组都反转过来。求出每一次操作后整个序列的逆序对数。 \(1\le n\le20,1\le m\le10^6\) 。
我们先来回忆一下如何用归并排序求逆序对。归并排序时,左区间和右区间都已经有序了。我们维护两个单调移动的指针就可以求出跨区间产生的逆序对,再加上子区间内部的逆序对即可。
注意到这样一个性质:当反转 \(2^k\) 子段时,每个子段内的逆序对数变成了之前的顺序对数,而段与段之间的逆序对数没有改变。于是我们只要在归并排序的过程中,统计每一层产生的逆序对数和顺序对数。对于一个操作 \(k\) ,只要把长度不超过 \(2^k\) 层的逆序对数和顺序对数进行交换,更长的段保持不变即可。然后统计每一层的逆序对数就是这次查询的答案。和上题一样,如果用快速排序,则总的时间复杂度为 \(O(n\log^2n)\) ;如果用归并排序,则总的时间复杂度为 \(O((n+m)\log n)\) 。
Analysis of Pathes in Functional Graph
题意:有一个 \(n\) 个点 \(n\) 条边的带权有向图。要求从每个点出发走 \(k\) 条边经过的边权之和 \(s\) 和经过的最小边权 \(m\) 。 \(1\le n\le10^5,1\le k\le 10^{10}\) 。
注意到这样一个性质:对于 \(n\) 个点 \(n\) 条边的无向图,每一个连通块都是一个环和若干棵树;对于 \(n\) 个点 \(n\) 条边的有向图,每一个连通块都是一个有向环和若干有向棵树(子节点 \(\to\) 父节点)。
我们考虑从一个点出发的路径是怎么样的。如果这个点在环中,那么它会一直在环里走;如果这个点在树中,那么它会先沿着树边走进环,然后一直在环里走。于是我们可以用倍增的思想,设 \(f_{i,j}\) 表示从 \(i\) 出发走 \(2^j\) 步到达哪个点,同时再维护一下权值和和最小权值即可。由于边都是有向的,因此 \(f_{i,0}\) 都是确定的,我们甚至不需要遍历整个图,直接倍增即可。
贪心
积木大赛
注意到,对于每一处 \(h_i>h_j(i<j)\) ,那么 \(j\) 处可以在处理 \(i\) 处的同时填充;对于每一处 \(h_i<h_j(i<j)\) ,那必然要填补 \(h_j-h_i\) 的部分。故答案显然是 \(\sum\max(h_i-h_j,0)\) 。
国王游戏
记 \(\begin{aligned}K=\prod^{i-1}_{k=1}a_k\end{aligned}\) 。设第 \(i\) 个人拿到的金币数为 \(\begin{aligned}c_i=K/b_i\end{aligned}\) 。注意到对于第 \(i\) 个人而言,前面 \((i-1)\) 个人的排列顺序是无关的。因而我们只要考虑相邻两个人的排列顺序,从而达到最小化 \(\max(c_i,c_{i+1})\) 的目的。
如果排列顺序为 \((i,i+1)\) ,则 \(c_i=K/b_i,c_{i+1}=K\times a_i/b_{i+1}\) ;如果排列顺序为 \((i+1,i)\) ,则 \(c'_i=K/b_{i+1},c'_{i+1}=K\times a_{i+1}/b_{i}\) 。假设 \(c_i\le c_{i+1}\) ,则需满足 \(c_{i+1}\le\max(c'_i,c'_{i+1})\) ,即 \(a_i/b_{i+1}\le a_{i+1}/b_i\Rightarrow a_ib_i\le a_{i+1}b_{i+1}\) ;假设 \(c_i\ge c_{i+1}\) ,则需满足 \(c_i\le \max(c'_i,c'_{i+1})\) ,注意到 \(c_i\le c'_{i+1}\) ,故该式显然成立。于是有这样的贪心策略:对于 \((i,i+1)\) ,需满足 \(a_ib_i\le a_{i+1}b_{i+1}\) 。
火柴排队
首先考虑怎么最小化 \(\sum{(a_i-b_i)^2}\) 。注意到 \(\sum{(a_i-b_i)^2}=\sum(a_i^2+b_i^2)-2\sum a_ib_i\) ,而前者求和是固定的,故只需最大化后者。根据排序不等式容易得到, \(a_i\) 与 \(b_i\) 都为序列中第 \(i\) 大值时, \(\sum a_ib_i\) 最大。
其次注意到,我们可以固定 \(a\) ,只交换 \(b\) ,使得两者一一对应。由于只能交换相邻的两个数,故而就类似于冒泡排序,我们求一下逆序对数即可。
观光公交
记 \(t_i\) 为车到第 \(i\) 站的时间, \(la_i\) 为第 \(i\) 站最晚来的人的时间,则显然有 \(\begin{aligned}t_i=\max(t_{i-1},la_{i-1})+d_{i-1}\end{aligned}\) 。考虑这个时候使用加速,即令 \(d_{i-1}\) 减一,会产生什么影响。显然 \(t_{1}\sim t_{i-1}\) 不会发生变化,且 \(\exist r\ge i\) ,使得 \(t_i \sim t_r\) 都减一,这需要 \(\forall j\in[i,r],t_j\gt la_{j}\) 。如果有 \(v_i\) 个人要经过 \(i-1\to i\) 这一段旅程,那么 \(t_i\) 减一就会对答案产生 \(v_i\) 的贡献。故而 \(d_{i-1}\) 减一,就会使 \(t_i\sim t_r\) 都减一,从而对答案产生 \(\begin{aligned}\sum^{r}_{k=i}v_k\end{aligned}\) 的贡献。于是显然有贪心策略:每次都选对答案产生贡献最大的 \(i\) ,对其进行加速,一共找 \(k\) 次即可。
Distributing Parts
题意:一部音乐剧一共 \(n\) 部分,第 \(i\) 部分的音高在 \([c_i,d_i]\) 中。有 \(m\) 个人,第 \(i\) 个人可以演唱音高在 \([a_i,b_i]\) 中的部分,且可以唱 \(k_i\) 个部分。问是否存在一种分配方案使得 \(n\) 部分都能被演唱。 \(1\le n,m\le100000,1\le a_i,b_i,c_i,d_i,k_i\le10^9\)
我们对所有的部分按左端点 \(c_i\) 从小到大排序,依次分配每个部分。为了演唱 \([c,d]\) 这部分,要求有 \(a\le c\) ,故我们维护一个数据结构 \(S\) ,存储所有满足 \(a\le c\) 的区间 \([a,b]\) 。假设有区间 \([a_1,b_1],[a_2,b_2](a_1,a_2 \le c,d<b_2<b_1)\) ,因为小于 \(c\) 的部分都已经分配完了,所以左端点不会对答案产生影响。但对于右端点,显然是取 \(b_2\) 比 \(b_1\) 更优。故贪心策略为:选择 \(S\) 中 \(b\ge d\) 的最小的 \(b\) 对应的区间。那么我们就用 set
来维护 \(a\le c\) 的 \(b\) 即可,每次在 set
中查找第一个大于等于 \(d\) 的数即可。
Change-free
题意:现在有面值为 \(100¥\) 的纸币和 \(1¥\) 的硬币,纸币有无限多,但硬币只有 \(m\) 个。接下来 \(n\) 天,每天都要花费 \(c_i¥\) 。已知收银员在第 \(i\) 天找零 \(x¥\) 时,会产生 \(x\times w_i\) 的不满意度。要求最小化不满意度之和。 \(1\le n\le10^5,1\le m\le10^9\) 。
显然我们只用考虑 \(c_i\mod100\) 的部分,因为纸币随意取用的。我们每天都可以使用硬币,直到第 \(k\) 天发现硬币不够了。那就说明我们必须在 \(1\sim k\) 中的某一天找零。假设在第 \(i\) 天找零。原本我们要花费 \(c_i\) 个硬币,故找零后节省下了 \(c_i\) 个硬币,且找回了 \((100-c_i)\) 个硬币。因此无论 \(c_i\) 取何值,找零都能使我们获得 \(100\) 个硬币。而选择在第 \(i\) 天找零的代价为 \((100-c_i)\times w_i\) ,因此如果第 \(k\) 天硬币不够了,那么我们可以在 \(1\sim k\) 中的某一天花费最小的代价获得 \(100\) 个硬币,用小根堆维护代价即可。
Renovation
题意:现在计划拆除 \(m\) 个建筑,计划分 \(n\) 天实行。第 \(i\) 天政府有\(a_i\) 的预算,第 \(i\) 个建筑有一个估测费用 \(b_i\) 和实际的拆毁费用 \(p_i\) 。现在每天可以拆除的建筑的 \(\sum b\) 不能超过当天预算,预算如果没有用完可以累计到下一天。问最多可以拆毁的建筑数量。 \(1\le n,m\le100000,1\le a_i,b_i,p_i,\le10^9\) 。
类似上一题,我们考虑每个建筑在哪一天被拆。注意到当 \(a_k\ge b_i\) 时,该建筑可以在第 \(k\) 天拆,但还得看钱够不够。第 \(k\) 天剩下的钱是 \(\begin{aligned}\sum^{k}_{i=1}a_i-\sum p\end{aligned}\) 。注意到,满足 \(a_k\ge b_i\) 的最大的 \(k\) 是钱最多的那一天,同时这也是可以拆除第 \(i\) 座建筑的最后一天。
因此对于第 \(i\) 个建筑,我们只要在满足 \(a_k\ge b_i\) 的最大的 \(k\) 的那一天去考虑是否要拆除它即可。因为如果还存在 \(j<k\) ,满足 \(a_j\ge b_i\) ,我们考虑在第 \(j\) 天不拆,则可以将 \(p_i\) 的钱省到第 \(k\) 天,这样或许可以填补 \(j\sim k\) 天中的某天的需要。故而在第 \(j\) 天拆不会比在第 \(k\) 天拆更优。
如果在第 \(k\) 天有很多符合条件的建筑,我们显然是选择 \(p\) 最小的拆除。如果第 \(k\) 天还有钱剩余,我们注意到这些钱只能被用在第 \(k\sim n\) 天,对于前 \((k-1)\) 天是不可用的,故而我们可以倒着枚举每一天前往哪里花。
每一天我们都把能用钱(不一定能直接拆毁)的建筑加入一个小根堆中,每次从堆中选择 \(p\) 最小的建筑拆除。若堆中 \(p\) 最小的建筑也无法拆除,加入还剩 \(v\) 的钱,那么就把这些钱都投入到这个建筑中,把 \(p\) 赋值为 \(p-v\) 。这样做的意义实际上就是为这个建筑攒 \(v\) 的钱,为了能在某一天将其拆除。
Bear and Paradox
题意:你在打一场比赛,一共有 \(n\) 道题,完成第 \(i\) 题需要花费 \(t_i\) 分钟。令 \(T=\sum t_i\) ,那么在第 \(x\) 分钟结束时完成第 \(i\) 题会使你获得 \(p_i(1-cx/T)\) 的分数,其中 \(c\in[0,1]\) 。在任何情况下你都会采取最优的策略做题,使得分数之和最大。先要求最大的 \(c\) ,使得不存在这样的情况:存在一对题目 \((i,j)\) ,满足 \(p_i<p_j\) 且第 \(i\) 题的得分大于第 \(j\) 题的得分。 \(1\le n\le150000,1\le p,t\le10^8\) 。
显然我们要分析一下给定 \(c\) 之后,最优策略如何确定。如果做题顺序为 \((i,j)\) 优于 \((j,i)\) ,则需满足:\(\begin{aligned}p_i(1-c(x_0+t_i)/T)+p_j(1-c(x_0+t_i+t_j)/T)>p_j(1-c(x_0+t_j)/T)+p_i(1-c(x_0+t_i+t_j)/T)\end{aligned}\) 化简得: \(p_jt_i<p_it_j\) ,即 \(t_i/p_i<t_j/p_j\) ,我们应该按照 \(t_i/p_i<t_j/p_j\) 排序后的顺序做题。但是注意,如果 \(t_i/p_i=t_j/p_j\) ,那么 \(i\) 和 \(j\) 无论先做哪题最优解,但是它们实际的得分是不同的,越早做分越高,因此还需要特判一下。
Balanced Sequence
题意:有 \(n\) 个括号字符串,你可以把它们按照一个排列拼接在一起。要求最大化拼起来的串中的括号匹配子序列(不一定要连续)的长度。 \(1\le n\le 100000\),字符串长度之和 \(\le5000000\) 。
首先注意到,如果串中已经有匹配的括号子序列,则我们可以将其删去并计入答案,如 (()(()
变为 ((
。因为这些子序列是一定能被选入最终答案的。经过这步操作可以使串里不再有匹配的括号子序列了。
那么对于没有匹配括号子序列的串而言,有且仅有三种形式:
((...((
))...))
))...))((...((
很自然的一个想法是,将 \(1\) 类串尽可能往左放, \(2\) 类串尽可能往右放。而对于 \(3\) 类串,我们还需要区分是左括号多还是右括号多。若右括号多,则往左放;反之,则往右放。
注意到这样的两个串: )(((
和 ))(((
,都是左括号多,应该如何决定顺序呢?显然把前者放左边更好。因为右括号需要和左边的左括号匹配,有可能此时的左括号还不够匹配,那么就会浪费一部分右括号,显然右括号更少地往左放能减少浪费。其他情况同理。
Delicious Apples
题意:有一个长为 \(L_0\) 的环,环上有 \(n\) 棵苹果树,每棵树上有 \(a_i\) 个苹果。一个人在 \(0\) 点,他有一个篮子,篮子每次最多只能装 \(k\) 个苹果。求要装完所有的苹果并且回到 \(0\) 点的最小路程。保证给出的苹果树的坐标是顺时针的。 \(1\le n,k,\sum a_i ,L_0\le 10^9\) 。
假设走了 \(x\) 的路程后篮子已经装满了。如果 \(2x<L_0\) ,即不过中点时,最佳路径是原路返回;反之,则最佳路径是走一圈回到起点。可以证明我们最多只会跨过中点一次,因为如果要走两圈,那么可以调整为两次不过中点的采摘。
于是现在就分成了三种情况:
- 顺时针不过中点
- 逆时针不过中点
- 过中点(最多一次)
我们可以预处理单测不过中点采摘的花费。 \(L_i\) 表示顺时针距离最近的前 \(i\) 个苹果的花费, \(d_i\) 表示起点顺时针距离第 \(i\) 近的距离。 \(L_i=L_{i-k}+d_i\) ,我们可以贪心地把后 \(k\) 个和第 \(i\) 个一起采摘回来,可以最小化 \(L_{i-k}\) ,进而得到最小的 \(L_i\) 。 \(R_i\) 同理。
对于跨越中点的采摘,一定是去摘离起点最远的那些苹果。枚举这次在左边摘 \(i\) 个,右边摘 \((k-i)\) 个。假设本来左边有 \(p\) 个,右边有 \(q\) 个。不过中点时答案为 \(2(L_p+R_q)\) ,过中点时答案为 \(2(L_{p-i}+R_{q-(k-i)})+L_0\) ,取最小值就是答案。
*搜索
dp
区间dp
合并果子
设 \(f_{i,j}\) 表示合并区间 \([i,j]\) 的最小代价, \(\begin{aligned}s_i=\sum^{i}_{k=1}a_k\end{aligned}\) ,显然有 \(\begin{aligned}f_{i,j}=\min(f_{i,j},f_{i,k}+f_{k+1,j}+s_j-s_{i-1})\end{aligned}\) 。
括号匹配
题意:给出一个只有 ()[]
四种字符组成的字符串,取出一个最长的子序列使得他们满足括号匹配,求最长匹配长度。
设 \(f_{i,j}\) 表示区间 \([i,j]\) 内的最长匹配长度。若 \(\begin{aligned}s_i=s_j\end{aligned}\) ,则直接累加 \(\begin{aligned}f_{i,j}=f_{i+1,j-1}+2\end{aligned}\) 。对于一般情况可以直接合并,即有, \(\begin{aligned}f_{i,j}=\max(f_{i,j},f_{i,k}+f_{k+1,j})\end{aligned}\) 。
Palindrome
题意:给定一个字符串 S ,问需要至少插入几个字符能使其变为回文串。 \(|S|\le5000\) 。
设 \(f_{i,j}\) 表示使区间 \([i,j]\) 变成回文串的最小插入次数,考虑简单分类讨论,则有 \(f_{i,j}=\left\{\begin{matrix} f_{i+1,j-1}&iff&s_i=s_j\\ \min(f_{i+1,j},f_{i,j-1})+1&iff&s_i\neq s_j \end{matrix}\right.\) 。注意此时枚举状态时,我们令 \(i:n\sim1,j:i\sim n\) 。
Again Palindrome
题意:给定一个字符串 S ,问有几种删字符的方案能使其变为回文串(不删也是一种方案,但全删不可以)。 \(|S|\le5000\) 。
设 \(f_{i,j}\) 表示删字符后使区间 \([i,j]\) 变为回文串的方案数。若 \(s_i\neq s_j\) ,简单容斥一下就好;若 \(s_i=s_j\) ,考虑 \(f_{i+1,j}+f_{i,j-1}\) 会将中间的 \([i+1,j-1]\) 的部分算两遍,但因为 \(s_i=s_j\) ,故会增加 \(f_{i+1,j-1}\) 种方案,因为本身 \([i+1,j-1]\) 的部分已经是回文了,此时还有一个特例,即 \([i+1,j-1]\) 全删,而字符串 \([s_is_j]\) 同样是回文串,故再加一。于是得到方程: \(f_{i,j}=\left\{\begin{matrix} f_{i+1,j}+f_{i,j-1}+1&iff&s_i=s_j\\ f_{i+1,j}+f_{i,j-1}-f_{i+1,j-1}&iff&s_i\neq s_j \end{matrix}\right.\) 。
String painter
题意:给定字符串 A 和 B,每次操作可以把一个区间变成同一个字符,问最少需要多少次操作能把 A 变成 B 。 \(|A|,|B|\le100\) 。
设 \(f_{i,j}\) 表示 \([A_i...A_j]\) 变为 \([B_i...B_j]\) 的最少操作数。考虑直接这样做转移很麻烦,于是我们先考虑怎么从空串变成 B 。设 \(g_{i,j}\) 表示空串变为 \([B_i...B_j]\) 的最少操作数。
考虑如果 \(B_{i}=B_{i+1}\) 则显然在涂色时,可以由 \([i+1,j]\) 直接向左扩展为 \([i,j]\) ,而 \(B_j=B_{j-1}\) 或 \(B_i=B_j\) 时同理。于是不难得到方程:\(g_{i,j}=\left\{\begin{matrix} \min(g_{i,j},g_{i+1,j})&iff&B_i=B_{i+1}&or&B_i=B_j\\ \min(g_{i,j},g_{i,j-1})&iff&B_j=B_{j-1}&or&B_i=B_j \end{matrix}\right.\) 。
然后我们考虑由 \(g_{i,j}\) 为踏板来转移 \(f_{i,j}\) 。此时我们可以改设 \(f_i\) 表示从 \([A_1...A_i]\) 变为 \([B_1...B_i]\) 的最小操作数。
考虑先枚举 \(i\) ,若 \(A_i=B_i\) ,则不需要涂色,直接继承上一状态;若 \(A_i\neq B_i\) ,则枚举 \(j\) 表示从 \(j\) 处开始往后刷,使得 \(f_{j}+g_{j+1,i}\) 最小。于是不难得到方程: \(f_{i}=\left\{\begin{matrix} \min(g_{1,i},f_{i-1})&iff&A_i=B_i\\ \min(g_{1,i},f_{j}+g_{j+1,i})&iff& A_i\neq B_i \end{matrix}\right.\) 。
搬寝室
题意:有 \(n\) 个行李,每个行李有一个重量。现在你要通过 \(k\) 次操作搬走 \(2k\) 个行李。每次左右手各拿一个行李,其重量分别为 \(x\) 和 \(y\) 。每次操作的疲劳度为 \((x-y)^2\) ,要求最小化疲劳度。 \(2\le 2k\le n\le2000\) 。
首先注意到一个显然的贪心策略,即对每个行李排序后,要选取的必然是相邻的两个行李。设 \(f_{i,j}\) 表示前 \(i\) 个行李中选取了 \(j\) 组行李的最小疲劳度。每次我们考虑选或不选。若选,则要选第 \(i\) 和第 \(i-1\) 个行李且此时要满足在前 \(i-2\) 个行李中已经选取了 \(j-1\) 组行李。于是不难得出方程:\(\begin{aligned}f_{i,j}=\min(f_{i-1,j},f_{i-2,j-1}+(a_i-a_{i-1})^2)\end{aligned}\) 。
配对
题意:有一个长度为 \(n\) 的序列,每个数都是 \(1\sim K\) 中的整数。现在有一些位置的数被遮住了,用 \(-1\) 表示。你可以往这些位置中填入 \(1\sim K\) 中的数,要求最小化逆序对数。 \(1\le n\le10000,1\le K\le 100\) 。
首先注意到一个显然的贪心策略,即我们考虑填入的数肯定是单调不降的。考虑如果填入的数 \(a_i>a_j(i<j)\) ,则会在填入的数中产生额外的逆序对,可以证明这样是不优的。
设 \(f_{i,j}\) 表示当前填到前 \(i\) 个未知位且第 \(i\) 个未知位上填的数为 \(j\) 的逆序对数,设 \(g_{i,j}\) 表示 \(a[1...i-1]\) 中大于 \(j\) 的个数, \(h_{i,j}\) 表示 \(a[i+1...n]\) 中小于 \(j\) 的个数。
我们只需要考虑填入一个数后原序列会产生的贡献,故有方程: \(\begin{aligned}f_{i,j}=\min(f_{i,j},f_{i-1,k}+g_{i,j}+h_{i,j})(k\le j)\end{aligned}\) 。最后我们再加上原数列的逆序对数即可。
背包问题
01背包与完全背包
题意:有 \(n\) 个物品和一个容量为 \(V\) 的背包,第 \(i\) 个物品的费用为 \(c_i\) ,价值为 \(w_i\) 。每个物品最多能被选取一次,要求最大化价值。
注:本节的除法默认向下取整。
设 \(f_{i,j}\) 表示从前 \(i\) 个物品中选出的体积为 \(j\) 的最大价值。显然有 \(\begin{aligned}f_{i,j}=\max(f_{i-1,j},f_{i-1,j-c_i}+w_i)\end{aligned}\) ,分别表示不选/选当前物品。
考虑优化第一维空间 \(i\) ,我们可以在每次枚举时令 \(j:V\sim0\) ,从而保证了每次访问到的 \(f_{j-c_i}\) 都是上一个 \(i\) 的值,此时定有一个分界点使得之前为 \(f_{i-1}\) 的值,之后为 \(f_i\) 的值,从而保证每个物品只被选取一次。这就是01背包,时间复杂度为 \(O(nV)\) 。
而完全背包是指每个物品都可以被选取无限多次。仍设 \(f_{i,j}\) ,于是同样有 \(\begin{aligned}f_{i,j}=\max(f_{i-1,j},f_{i-1,j-k\times c_i}+k\times w_i)\end{aligned}(0\le k\times c_i\le j)\) 。时间复杂度为 \(O(nV\sum (V/c_i))\) ,不可以接受,考虑优化。
类似01背包的一维优化,我们可以在每次枚举时令 \(j:0\sim V\) ,从而保证了每次访问到的 \(f_{j-c_i}\) 都是这一个 \(i\) 的值,此时定有一个分界点使得之前为 \(f_{i}\) 的值,之后为 \(f_{i-1}\) 的值,从而保证每个物品可以被选取无限次(因为在我选这个物品的时候,这个物品可能已经被选过了)。这就是完全背包,时间复杂度为 \(O(nV)\) 。
多重背包与优化
题意:有 \(n\) 个物品和一个容量为 \(V\) 的背包,第 \(i\) 个物品的费用为 \(c_i\) ,价值为 \(w_i\) 。每个物品最多能被选取 \(a_i\) 次,要求最大化价值。
仍设 \(f_{i,j}\) ,于是有 \(\begin{aligned}f_{i,j}=\max(f_{i-1,j},f_{i-1,j-k\times c_i}+k\times w_i)\end{aligned}(0\le k\le a_i)\) 。此时的时间复杂度为 \(O(nV\sum a_i)\) ,不可以接受,考虑优化。注意到这个不同于完全背包,如果我们正序枚举 \(j\) ,则我们不能保证每个物品最多仅能被选取 \(a_i\) 次,所以要考虑新的方法。
二进制拆分优化
考虑如 \((15)_{10}=(1111)_2\) ,表示 \(15=1+2+4+8\) ,分别可以被拆分成 \(4\) 个物品,从而使多重背包问题被转化为了01背包问题。其正确性显然,因为对于每一个二进制位都有选或不选,于是此时 \(k\) 个二进制位就可以表示 \(0\sim 2^{k}-1\) 个物品。然后考虑更一般的情况,如 \((12)_{10}=(1100)_2\) ,表示 \(12=1+2+4+5\) 。注意到此时我们不能令 \(12=8+4\) ,这样就会使得有些数无法被表示。
故而我们对每个 \(a_i\) ,都先拆分成 \(1,2,4,...,2^k\) 个,且 \(k\) 是满足 \(2^k-1<n\) 的最大值。此时我们可以拼出 \(0\sim 2^k-1\) 的任何数量。如果再加一份 \(x\) ,就可以表示 \(x\sim x+2^k-1(x\le 2^k)\) 中的数量,此时上述两个区间有重复,故能完美覆盖 \(0\sim a_i\) 中的情况。如 \(a_i=12\) 时,上述两个区间分别为 \([0,7]\) 和 \([5,12]\) ,覆盖了 \([0,12]\) 。
设 \(\begin{aligned}Max=\max(a_i)\end{aligned}\) ,于是我们可以将物品拆分为 \(O(\log Max)\) 个最多仅能被选取 \(1\) 次的物品,此时变为了01背包问题,时间复杂度为 \(O(V\sum(\log a_i))\) 。
单调队列优化
对于 \(f_{i,j}\) ,令 \(b_i=\min(a_i,j/c_i)\) ,于是有 \(\begin{aligned}f_{i,j}=\max(f_{i-1,j},f_{i-1,j-k\times c_i}+k\times w_i)\end{aligned}(0\le k\le b_i)\) 。我们观察到 \(f_{i,j}\) 的值会从 \(f_{i-1,j-k\times c_i}\) 转移过来。对于 \(\left \{ j-k\times c_i|0\le k\le b_i \right \}\) 中的这些数,它们模 \(c_i\) 是同余的。
我们令 \(mod=j\mod c_i,div=j/c_i\) ,故 \(j=div\times c_i+mod\) ,于是有 \(\begin{aligned}f_{i,j}=\max(f_{i-1,j},f_{i-1,(div-k)\times c_i+mod}+k\times w_i)\end{aligned}(0\le k\le b_i)\) 。
为了美观,我们用令 \(k'=div-k\) 并且把式子中的 \(f_{i-1,j}\) 略去,同时还注意到,对于确定的 \((i,j)\) ,其对应的 \((div,mod)\) 也是唯一确定的。于是有方程: \(\begin{aligned}f_{i,j}&=\max(f_{i-1,k'\times c_i+mod}+(div-k')\times w_i)(0\le k\le b_i)\\&=\max(f_{i-1,k'\times c_i+mod}-k'\times w)(0\le k\le b_i)+div\times w_i\end{aligned}\)
我们观察上面的式子,注意到 \(\left \{mod,c_i+mod,2c_i+mod,...,j\right \}\) 就是求 \(j\) 前面的 \((b_i+1)\) 个数对应的 \(f_{i-1,k'\times c_i+mod}-k'\times w_i\) 的最大值。对于每个 \(i\) 而言, \(b_i+1\) 是固定的,于是问题转化为求一个固定长度的滑窗内的最大值。
我们维护一个单调下降的队列,每次加入的时候加入队尾,保证队头到队尾单调递减。弹出队头,直到满足队头在滑窗内,队头的值就是这个滑窗内的最大值。因为显然如果 \(a_i<a_j(i<j)\) ,那么 \(a_i\) 是毫无作用的。这样做显然是线性的,故我们先枚举 \(mod\) ,再枚举 \(div\) ,用单调队列求 \(f_{i,j}\) ,从而使时间复杂度仍保持在了 \(O(nV)\) 。
Dima and Salad
题意:有 \(n\) 个数对 \((a_i,b_i)\) ,现在要求取出若干个数对使得 \(\sum a_i/\sum b_i=k\) ,要求最大化 \(\sum a_i\) 。 \(1\le n\le100,1\le k\le10,1\le a_i,b_i\le100\) 。(此处并不为下取整除法)
由题意, \(\sum a_i/\sum b_i=k\Rightarrow \sum(a_i-k\times b_i)=0\) 。于是本题被转化为了,价值为 \(a_i\) ,费用是 \(c_i=(a_i-k\times b_i)\) 的01背包。但注意到 \(c_i\) 可正可负,且要求 \(\sum c_i=0\) 。于是,我们设 \(f_i\) 和 \(g_i\) 分别表示 \(c_i\ge0\) 或 \(c_i<0\) 时的最大价值,最后枚举 \(V:1\sim 10000\) ,答案即为 \(\max(f_V+g_V)\) 。
Fire
题意:有 \(n\) 个物品,每个物品的价值为 \(p_i\) ,获得物品需要花费 \(t_i\) 的时间。若物品在 \(d_i\) 的时间内没有被获得则会销毁。要求最大化价值。 \(1\le n\le100,1\le p_i,t_i\le20,1\le d_i\le2000\) 。
典型的01背包问题,设 \(f_i\) 表示在前 \(i\) 个时间单位下最大的价值和,于是有 \(\begin{aligned}f_{i}=\max(f_{i},f_{i-t_j}+p_j)(0\le i-t_j\le d_j)\end{aligned}\) 。因此我们先对物品按 \(d_i\) 从小到大排序,然后依次更新。
Coins
题意:有 \(n\) 种面额的硬币,第 \(i\) 个硬币的面额为 \(v_i\) ,个数为 \(c_i\) 。问最多能搭配出多少种不超过 \(m\) 的金额。 \(1\le n\le100,1\le v_i,m\le100000,1\le c_i\le1000\) 。
典型的多重背包问题,设 \(f_{i,j}\) 表示前 \(i\) 个硬币能否拼出为 \(j\) 的金额 。由于是存在性问题,我们可以直接枚举。令 \(j:v_i\sim m\) ,若 !f[j]&&f[j-v[i]]&&sum[j-v[i]]<c[i]
,则可以转移,其中 \(sum_j\) 表示为了拼出 \(j\) 使用了几个 \(i\) 。时间复杂度为 \(O(nm)\) 。
PolandBall and Gifts
题意:有 \(n\) 个人,第 \(i\) 个人相送第 \(p_i\) 个人一份礼物。 \(p\) 是一个排列,且 \(p_i\neq i\) 。很遗憾现在有 \(k\) 个人忘记带礼物了,如果 \(i\) 忘带礼物了,那么 \(i\) 和 \(p_i\) 都不会收到礼物。问无法收到礼物的人最多和最少有几个。 \(1\le k\le n\le10^6\) 。
本题实际上是 \(i\to p_i\) 的一张有向图。而每个点的入度和出度都为 \(1\) ,故会形成若干个环。
考虑最大化没有收到礼物的人数。对于一个长度为 \(k\) 的偶环,只需要有 \(k/2\) 个人忘带就能使 \(k\) 个人都收不到礼物;对于一个长度为 \(k+1\) 的奇环,只需要有 \((k+1)/2\) 个人忘带,就能使 \(k\) 个人都收不到礼物。于是贪心即可。
考虑最小化没有收到礼物的人数。如果有一个长度为 \(m\) 的环,那么只要让 \(m\) 个人都忘带就会有 \(m\) 个人收不到礼物。因此如果我们能找到若干个环,使得长度总和为 \(k\) ,那么就能有 \(k\) 个人收不到礼物;否则就会有一个环不能完全覆盖,这样会额外牵连一个人(因为此时无法构成一个环,而变成了一条链),故答案是 \(k+1\) 。
此时问题就变成了有 \(m\) 个环,第 \(i\) 个环的长度为 \(l_i\) ,数量有 \(c_i\) 个,问能否凑出长度和为 \(k\) 的方案。此时就被转化为了多重背包问题。考虑二进制拆分优化,拆成 \(\log c_i\) 个物品做01背包,时间复杂度为 \(0(n\log n )\) 。
飞扬的小鸟
设 \(f_{i,j}\) 表示到达第 \(i\) 列第 \(j\) 行最少需要的点击次数。枚举在 \(i-1\) 列点 \(k\) 次屏幕,有 \(\begin{aligned}f_{i,j}=\min(f_{i-1,j},f_{i-1,j-k\times up_{i-1}}+k)\end{aligned}\) 。考虑当成多重背包来做(因为此时限制了点击的次数需要在管子的空隙内),这样的时间复杂度为 \(O(nm^2)\) ,用单调队列优化可以做到 \(O(nm)\) 。
当然本题也可以巧妙地当成完全背包来做(即我们认为此时没有管子)。我们先用 \(\begin{aligned}f_{i,j}=\min(f_{i,j},f_{i-1,j-up_{i-1}+1})\end{aligned}\) 来更新 \(f_{i,j}\) ,表示点击了一次屏幕。然后用 \(\begin{aligned}f_{i,j}=\min(f_{i,j},f_{i,j-up_{i-1}+1})\end{aligned}\) 来更新 \(f_{i,j}\) ,表示在点击了一次屏幕的基础上,点击了若干次屏幕。在更新完所有 \(f_{i,j}\) 后,我们再令管子对应的部分的 \(f_{i,j}=INF\) 。时间复杂度 \(O(nm)\) 。
如果最终我们不能飞到第 \(n\) 列的位置,换言之此时所有的 \(f_{n,i}=INF\) 。于是,我们枚举 \(i:1\sim n\) ,看是哪一个位置时,所有的 \(f_{i,j}=INF\) ,即表示我们此时飞不到 \(i\) 的位置,然后我们判断一下在此之前出现了几根管子即可。注意还要处理一下飞到天花板上时的细节。
树形dp
树上最长链
题意:给定一棵树,每条边有边权,计算一条最长链。要求复杂度 \(O(n)\) 。
首先我们注意到,这条最长链可以经过根的左子树和右子树,于是我们势必要维护两个不同的子树的最长链加起来。
设 \(f_u\) 表示以 \(u\) 为根的子树内的最长链。那么会有两种情况: 不经过 \(u\) 点的最长链或经过 \(u\) 点的最长链。前者很显然, \(\begin{aligned}f_u=\max(f_u,f_v)\end{aligned}\) 。后者根据上文我们提到的性质,要选择 \(u\) 的两个子节点往下走的最长路再加上子节点走向 \(u\) 的路径长度,从而更新 \(f_u\) 。于是,我们设 \(g_u\) 表示从 \(u\) 往下走的最长路,显然 \(\begin{aligned}g_u=\max(g_u,g_v+w)\end{aligned}\) 。我们对于每个点而言,要维护其两个不同的子树中向下的最长路 \(maxg_1\) 和次长路 \(maxg_2\) , \(\begin{aligned}f_u=\max(f_u,maxg_1+maxg_2)\end{aligned}\) 。
最大权值子树和
题意:给定一棵树,每个点有权值(可正可负),求一个子树(可以认为是连通子图)使得权值和最大。要求时间复杂度 \(O(n)\) 。
设 \(f_u\) 表示以 \(u\) 为根的子树内最大的权值和,同时我们增设一维 \(1/0\) 表示选或不选点 \(u\) 。对于前者,由于其子节点都是连通的,选取为正权值和的子节点即可。而对于后者,由于各儿子节点间不相互连通,于是我们选取正权值和最大的一个子节点。于是有方程: \(\begin{aligned}f_{u,1}=w_u+\sum_{v\in son_u}\max(f_{v,1},0)&&f_{u,0}=\max(f_{v,1},f_{v,2},0)\end{aligned}\)
The more, The better
题意:有一棵树,每个点有点权。选出一棵有 \(m\) 个点的子树,使其点权和最大。 \(1\le m\le n\le 200\) 。
设 \(f_{u,k}\) 表示在以 \(u\) 为根的子树中选取了 \(k\) 个点的最大权值和。显然 \(\begin{aligned}f_{u,1}=w_u\end{aligned}\) ,因为只能取 \(u\) 自己。然后我们枚举 \(u\) 的所有子节点 \(v\) ,于是有方程: \(\begin{aligned}f_{u,V}=\max(f_{u,V},f_{u,V-k}+f_{v,k})(1\le k\lt\min(sz_v,V),1\le V\le \min(sz_u,m))\end{aligned}\) 。
每次我们从子节点更新到当前点的复杂度为 \(O(n^2)\) ,故本题的总复杂度为 \(O(n^3)\) 。
Anniversary party
设 \(f_{u,1/0}\) 表示在以 \(u\) 为根的子树内选或不选 \(u\) 点的最大权值和。若选 \(u\) ,则其儿子都不能选;若不选 \(u\) ,则其儿子都可以考虑选或不选。于是有方程: \(\begin{aligned}f_{u,1}=w_u+\sum_{v\in son_u}f_{v,0}&&f_{u,0}=\sum_{v\in son_u}\max(f_{v,1},f_{v,0})\end{aligned}\)
ZYB's Tree
题意:给定一棵树,每条边的边权为 \(1\) 。对于每个点,求出距离这个点不超过 \(K\) 的点的个数。 \(1\le n\le 500000,1\le K\le10\) 。
设 \(f_{u,k}\) 表示以 \(u\) 为根的子树内(含 \(u\) ),距离 \(u\) 不超过 \(k\) 的点的个数,显然有 \(\begin{aligned}f_{u,k}=\sum_{v\in son_u}f_{v,k-1}\end{aligned}\) 。
设 \(g_{u,k}\) 表示以 \(u\) 为根的子树外(含 \(u\) ),距离 \(u\) 不超过 \(k\) 的点的个数。\(g_{u,k}\) 可能是从父亲节点 \(fa\) 直接转移过来的或是从兄弟节点 \(bro_u\) 转移过来。前者累加即可,而后者是 \(bro_u\to fa\to u\)。于是有 \(\begin{aligned}g_{u,k}=g_{fa,k-1}+\sum_{v\in bro_u }f_{v,k-2}=g_{fa,k-1}+\sum_{v\in son_{fa}}f_{v,k-2}-f_{u,k-2}\end{aligned}\) 。
最后注意每个点 \(u\) 对答案的贡献为 \(f_u+g_u-1\) ,因为 \(u\) 本身被重复计算了。两次 dfs ,第一次为自下而上计算,第二次为自上而下计算,复杂度 \(O(n)\) 即可解决本题。
Apple Tree
题意:给定一棵树,每个点有点权。一个人从 \(1\) 号点出发走 \(K\) 步,每次访问某个节点时,会获得该点的权值,并修改该点的权值为 \(0\) ,要求最大化获得的权值。 \(1\le n\le 100,1\le K\le 200\) 。
设 \(f_{u,k}\) 表示在以 \(u\) 为根的子树内走 \(k\) 步能获得的最大权值和。虽然权值只会得到一次,但路径是可以重复走的,于是我们设计状态时需要额外设计一维 \(1/0\) 表示是否会回到 \(u\) 点。
前者很简单,方程为: \(\begin{aligned}f_{u,V,1}=\max(f_{u,V,1},f_{u,V-k,1}+f_{v,k-2,1})(1\le k \le V\le K)\end{aligned}\) 。因为我们可以先在 \(u\) 的子树内任意走 \(V-k\) 步回到 \(u\) ,而 \(u\to v\to u\) 需要消耗 \(2\) 步,所以我们可以在 \(v\) 的子树内任意走 \(k-2\) 步回到 \(v\) 。
而后者有两种情况:我们可以先走其他儿子回到 \(u\) 再走到 \(v\) ,也可以先走 \(v\) 回到 \(u\) 再走到其他儿子。方程为: \(\begin{aligned}f_{u,V,0}=\max(f_{u,V,0},f_{u,V-k,1}+f_{v,k-1,0},f_{u,V-k,0}+f_{v,k-2,1})(1\le k \le V\le K)\end{aligned}\) 。因为对于第一种情况,我们可以先在 \(u\) 的子树内任意走 \(V-k\) 步回到 \(u\) ,而\(u\to v\) 需要消耗 \(1\) 步,所以我们可以在 \(v\) 的子树内任意走 \(k-1\) 步且不回到 \(v\) (因为注意到此时回到 \(v\) 肯定不优于继续向下走);对于第二种情况, \(u\to v\to u\) 需要消耗 \(2\) 步,我们可以先在 \(v\) 的子树内任意走 \(k-2\) 步, 然后在 \(u\) 的子树内任意走 \(V-k\) 步且不回到 \(u\) 。
最后注意一个小细节,除不回到 \(u\) 的第一种情况外,剩下两种情况都需要满足一个条件,即 \(2|k\) 。因为要回到点 \(v\) ,必然需要路径长度为偶数。那为啥有种情况不需要考虑呢?当然是因为 \(V\) 是从大到小枚举的, \(f_{u,V-k,1}\) 肯定是合法情况,同时还不需要回到点 \(v\) 。
Information Disturbing
题意:给出一棵树,每条边有边权。现在要求切断其中的一些边,使得任意一个叶子没有走到祖先的路。要求切断的边的总权值不能超过 \(m\) ,求所有方案中切断的最大边权的最小值。 \(1\le n\le1000,1\le m\le 10^6\) 。
考虑二分。我们可以二分最大边权 \(maxL\) ,然后统计最小花费要小于等于 \(m\) ,若某条边的边权大于 \(maxL\) ,则显然不能断这条边。设 \(f_u\) 表示点 \(u\) 与其所有叶子节点断开的最小花费,若 \(u\) 为叶子节点,则设 \(\begin{aligned}f_u=INF\end{aligned}\) 。反之,我们注意到只有两种情况,即断开边 \((u,v,w)\) 或点 \(v\) 与其叶子结点已经被断开了。于是显然有方程: \(\begin{aligned}f_u=\sum_{v\in son_u}(\min(w,f_v)\times[w\le maxL]+f_v\times[w\gt maxL])\end{aligned}\) 。
Alternating Tree
题意:给出一棵树,每个点有点权 \(V\) 。对于一条路径 \(u_1\to u_2\to...\to u_{m-1}\to u_{m}\) ,其权值 \(\begin{aligned}A(u_1,u_m)=\sum^{m}_{i=1}(-1)^{i+1}\times V_{u_i}\end{aligned}\) 。求所有路径的权值之和。 \(1\le n\le200000\) 。
首先注意到一个很显然的性质,偶数长度的路径对答案的贡献为 \(0\) ,因为显然正着走一遍和反着走一遍后,贡献就抵消了。因此,我们只需要考虑路径长度为奇数时,每个点对答案的贡献,也即是说有几条路径会被加,几条路径会被减。注意到对于点 \(fa\) ,肯定是选择其两个子节点 \(u,v\) 同时满足其到点 \(fa\) 的路径上同时有奇数个点或偶数个点,因为要同时这里点 \(fa\) 被计算了两次,所以实际 \(u\to v\) 的路径上有奇数个点,反之亦然。
对于点 \(u\) 而言有两种情况,路径在以 \(u\) 为根的子树内,或路径有一部分在 \(u\) 的祖先内。设 \(f_{u,1/0}\) 表示以 \(u\) 为根的子树内(含 \(u\) )到 \(u\) 的路径有奇数/偶数的点的个数;设 \(g_{u,1/0}\) 表示以 \(u\) 为根的子树外(含 \(u\) )到 \(u\) 的路径有奇数/偶数的点的个数;设 \(h_{u,1/0}\) 表示所有点(含 \(u\) )到 \(u\) 的路径有奇数/偶数个点的个数。
对于根节点 \(1\) 而言, \(h_{1}=f_{1}\) 。然后有一个重要的性质,即对于相邻点 \(u,v\) ,有 \(h_{u,0}=h_{v,1},h_{u,1}=h_{v,0}\) ,因为到 \(u\) 距离为奇数的点到 \(v\) 距离肯定为偶数,反之亦然。至此,我们可以用 \(f_1\) 表示所有的 \(h\) 了。若 \(u\) 的深度为奇数,则 \(h_u=f_u\) ;若 \(u\) 的深度为偶数,则 \(h_{u,1/0}=f_{u,0/1}\) 。
于是显然有 \(\begin{aligned}g_{u,1}=h_{u,1}-f_{u,1}+1&&g_{u,0}=h_{u,0}-f_{u,0}\end{aligned}\) ,因为自己到自己的路径上有奇数个点。
回到题目,每个点 \(u\) 对答案的贡献为 \(V_u\times( 2\times(g_{u,1}\times f_{u,1}-g_{u,0}\times f_{u,0})-1)\) 。因为根据最初提到的性质,从上往下或从下往上走到 \(u\) 的路径上有奇数个点保证了点 \(u\) 是第奇数个点,故贡献为正;而反之,则点 \(u\) 是第偶数个点,故贡献为负。乘二是因为路径的起点和终点交换后贡献仍然是一样的,减一是因为 \(u\to u\) 会重复计算一次。
*数位dp
字符串
哈希
Long Long Message
题意:给出两个字符串 \(S,T\) ,求它们的最长公共子串。 \(1\le |S|,|T|\le 10^5\) 。
原始的 DP 做法时间复杂度为 \(O(n^2)\) ,不可接受。我们可以二分答案 \(len\) ,计算 \(S\) 和 \(T\) 中所有长度为 \(len\) 的子串的哈希值,这一步是 \(O(n)\) 。然后比较 \(S\) 和 \(T\) 的哈希值集合中是否有相同的元素,可以再通过一步哈希找相同的值,这样总共仍然是 \(O(n)\) 的。总的时间复杂度为 \(O(n\log n)\) .
Kefa and Watch
题意:给出一个数字串,现在有两种操作:将 \([l,r]\) 中的所有数字都改为 \(d\) ;询问 \([l,r]\) 这个子串的周期是否为 \(d\) 。 \(1\le n\le 10^5\) 。
记 \(n=|S|\) 。考虑对于一个字符串 \(S\) 判断它的周期是否是 \(d\) ,只需要判断 \(S[1...n-d]\) 和 \(S[d+1...n]\) 是否相等即可。
我们可以用线段树来维护哈希值。对于一个区间 \([l,r]\) ,维护的是 \(S[l]k^{r-l+1}+\dots+s[r]\) 。那么合并两个区间的时候: \(t[p]=t[ls]k^{r-mid}+t[rs]\) 即可。对于覆盖标记: \(t[p]=d\times(k^{r-l}+k^{r-l-1}+\dots+k^0)\) 。上述操作每次都是 \(O(\log n)\) 。
Scissors
题意:给出两个字符串 \(S\) 和 \(T\) 。你需要在 \(S\) 中选出两个不相交的长度为 \(k\) 的子串,使得它们拼起来之后包含 \(T\) 。 \(1\le|T|\le2k\le|S|\le5\times 5\times10^5\) 。
如果 \(|T|\le k\) ,那么直接在 \(S\) 中找是否有 \(T\) 出现即可;否则,我们可以枚举 \(T\) 的分界点 \(i\) ,满足从 \(S\) 选出的两个串中,一个串的后缀是 \(T[1...i]\) ,另一个串的前缀是 \(T[i+1...|T|]\) 。
令 \(lpos_x\) 表示 \(S\) 中满足从它开始的长度为 \(x\) 个字符为 \(T[1...x]\) 的最小下标。为了保证这能成为长度为 \(k\) 的子串的后缀,还得满足条件 \(x+lpos_x\ge k\) ,否则没法取这个串,没有满足条件的下标就记为 \(-1\) 。同理,令 \(rpos_x\) 表示 \(S\) 中满足从它开始的长度为 \(x\) 个字符为 \(T[|T|-x+1...|T|]\) 的最大下标,同样得满足 \(rpos_x-x\le n-k+1\) ,否则没法取这个串,没有满足条件的下标就记为 \(-1\) 。
那么如何求 \(lpos\) 数组呢?首先求出 \(S\) 关于 \(T\) 的 \(extend\) 数组(后面扩展 KMP 会讲到),其中, \(extend_i\) 表示 \(S[i...|S|]\) 和 \(T\) 的最长公共前缀(也可以称为是 LCP )。那么 \(lpos_x\) 就是满足 \(extend_i\ge x\) 且有 \(x+i\ge k\) 的最小的 \(i\) 。直接暴力寻找是 \(O(n^2)\) 的,不可接受。注意到,我们只要从大到小枚举 \(x\) 即可,每次把 \(extend_i\ge x\) 的 \(i\) 加入一个集合,询问的就是 $i\ge k-x $ 的最小的 \(i\) ,这可以用一个 set
在 \(O(\log n)\) 的时间内二分查询。翻转 \(S\) 和 \(T\) ,就可以求出 \(rpos\) 数组。
最后我们枚举 \(x\le k\) ,检查 \(lpos_x\) 和 \(rpos_{|T|-x}\) 能否拼成答案即可。时间复杂度为 \(O(n\log n)\) 。
KMP
Power Strings
题意:给出一个字符串 \(S\) ,求 \(S\) 的一个最短的循环节 \(e\) ,使得 \(S\) 可以写成 \(ee...ee\) (共 \(|S|/|e|\) 个 \(e\) ),输出 \(|S|/|e|\) 的最大值。 \(1\le |S|\le 10^6\) 。
记 \(next[k]\) 为满足 \(S[1...k]\) 的前 \(x\) 个字符和后 \(x\) 个字符相同的最大的 \(x\) 。(有些时候我们也称该数组为 \(fail[k]\) )
记 \(len=|e|\) ,如果 \(len\) 可以被 \(len-next[len]\) 整除,那么我们就可以说 \(len-next[len]\) 是这个循环节的长度。因为 \(next[len]\) 就表示: \(S[1...next[len]]=S[next[len]+1...len]\) ,可以证明满足这一条性质的字符串具有长度为 \(len-next[len]\) 的循环周期。
否则, \(len=1\) 。因为如果存在一个长度为 \(d\) 的循环节,那么一定满足: \(S[1...len-d+1]=S[d+1..len]\) ,但是现在循环节的长度只可能是 \(len-next[len]\) ,如果它不是 \(len\) 的因子,那就没有可能了。时间复杂度为 \(O(n)\) 。
Seek the Name, Seek the Fame
题意:给出一个字符串 \(S\) ,求 \(S\) 所有的既是前缀又是后缀的子串。输出 \(i\) 代表 \(S[1...i]\) 。 \(1\le |S|\le 400000\) 。
由 \(next\) 数组可知,答案为 \(len,next[len],next[next[len]],\dots\) 。
Simpsons’ Hidden Talents
题意:给出两个字符串 \(S\) 和 \(T\) ,求既是 \(S\) 前缀又是 \(T\) 后缀的最长子串。\(1\le|S|,|T|\le 50000\) 。
首先我们可以把两个串拼起来,中间用一个分隔符 #
分隔,即变成 \(S#T\) 。然后由上一题可知,再枚举 \(len,next[len],next[next[len]],\dots\) ,找到小于 \(\min(|S|,|T|)\) 的最大值即可。
Om Nom and Necklace
题意:给出一个字符串 \(S\) ,判断其每个前缀是否可以表示成 \(ABAB...ABA\) 的形式。 \(A\) 和 \(B\) 都可以为空,但必须满足 \(A\) 有 \(k+1\) 个,而 \(B\) 有 \(k\) 个。 \(1\le|S|,k\le10^6\) 。
对于前缀 \(P\) ,我们可以把 \(P\) 拆成 \(SS...ST\) ,其中 \(T\) 是 \(S\) 前缀,这样表示有 \(k\) 个 \(S\) 作为 \(AB\) ,以及最后的 \(T\) 作为 \(A\) 。这样就可以用 KMP 来做了。首先, \(i-next[i]\) 就是 \(S[1..i]\) 这一段的最小循环节的长度,记为 \(e\) ,可以发现 \(e\) 的倍数 \(je\) 也是循环节。
什么情况下存在划分方案呢?当且仅当 \(i/(je)=k\) 或 \((je)|i\) 且 \(i/(je)=k+1\) ,这同样涵盖了 \(A\) 为空或 \(B\) 为空的情况。这样就可以求出 \(j\) 了,时间复杂度为 \(O(n)\) 。
GT考试
题意:给定数字串 \(A[1...m]\) ,构造一个数字串 \(X[1...n]\) ,使得串中不出现数字串 \(A\) ,求出方案数模 \(k\) 的结果。 \(1\le n\le10^9,1\le m\le20,1\le k\le1000\) 。
用 \(f_{i,j}\) 表示目前构造出的串是 \(X[1...i]\) ,匹配到 \(A\) 的第 \(j\) 个字符。枚举第 \(j+1\) 个数填 \(k\) ,如果 \(k\) 正好是 \(A[j+1]\) ,那么就会转移到 \(f_{i+1,j+1}\) ;否则就会沿着 \(next[j],next[next[j]],\dots\) 一直往回走,知道 \(A[next[j]+1]=k\) ,会转移到 \(f_{i+1,next[j]+1}\) 。
可以发现 \(f_i\) 到 \(f_{i+1}\) 的转移可以看做左乘一个转移矩阵。我们只要算出这个矩阵的 \(n\) 次幂,再乘上 \(f_0\) 就能得到我们想要的 \(f_n\) 了。我们可以用 KMP 构造这个转移矩阵,总的时间复杂度为 \(O(m^3\log n)\) 。