2025赛季做题记录
计数
【UR #1】外星人
归类:计数 dp
考虑到取模只会让数变小,所以从大到小模才有意义,先模小再模大数是没有意义的,因此考虑将数从大到小排序后顺序处理。
定义\(f_{i,j}\)表示考虑前\(i\)个数已经排好,使得现在的余数是\(j\)的方案数,考虑按照是否取模\(a_i\)分类转移。
如果要取模\(a_i\)那么就要紧接着,得到
否则就要舍弃掉\(a_i\),将其扔到后面小数里,方案数为\((n-i)\),则
[AGC013D] Piling Up
归类:计数 dp
考虑每次操作时的情况,取出的颜色与白球数量的关系:
WW
会使得数量 \(-1\)WB
数量不变 \(0\)BW
数量不变 \(0\)BB
会使得数量 \(+1\)
所以每个出球序列都对应到一个白球数量的变化序列,因此可以得到一个dp:定义\(f_{i,j}\)表示考虑前\(i\)次操作,序列中剩下\(j\)个白球的变化序列的方案数。
但是我们需要考虑初始时的白球数量,很naive的想法就是钦定所有\(f_{0,*}=1\),但这是错误的。
因为不难发现,两个白球数量序列在初始数量不同时可能会得到相同的取出颜色序列,也就是算重了。
因此考虑处理冗余,我们可以将所有算重的白球序列都向下平移,映射到同一个与\(0\)相交的结果,那么只统计与\(0\)相交,即数量到达过\(0\)的白球操作序列即可。
所以改进dp,\(f_{i,j,0/1}\)表示考虑前\(i\)次操作,序列中剩下\(j\)个白球,当前是否触碰到\(0\) 的变化序列的方案数。简单转移即可。
P2612 [ZJOI2012] 波浪
归类:计数 dp
考虑拆贡献,考虑一个\(P_i\),如果左边小/大那么贡献就是\(\pm P_i\),右边小/大贡献就是\(\pm P_i\),所以我们关注的并不是每个\(P_i\)的具体位置,而是与其相邻的数的大小关系。
所以考虑从小到大填数,即可钦定数的大小关系,然后用连续段dp,令
\(f_{i,j,k,l}\) 表示填好了\(1\sim i\)的数,形成\(j\)个连通块,目前总贡献为\(k\),并且左右边界中有\(l\)个被使用的方案数。
分类讨论数\(i\)放在什么位置:
-
不在边界上,且不与任何连通块相邻
此时左右的数一定是比\(i\)大的,因此贡献为\(-2i\),有\((j+1)\)个空位,由于不能放在边界上,所以总共有\((j+1-l)\)种方案:
\[f_{i+1,j+1,k-2i,l}\leftarrow^+ f_{i,j,k,l}(j+1-l) \] -
不在边界上,一端与连通块相邻 \((j\ge 1)\)
此时一边大一边小,贡献为\(0\),每个连通块的左右都可以放,所以有\((2j-l)\)种方案:
\[f_{i+1,j,k,l}\leftarrow^+ f_{i,j,k,l}(2j-l) \] -
合并两个连通块 \((j\ge 2)\)
此时两边都是小,贡献为\(2i\),中间有\((j-1)\)个位置放,于是:
\[f_{i+1,j-1,k+2i,l}\leftarrow^+ f_{i,j,k,l}(j-1) \] -
与边界相邻,且不与连通块相邻
此时一边是大的,贡献为\(-i\),并且剩余\((2-l)\)个边界,于是:
\[f_{i+1,j+1,k-i,l+1}\leftarrow^+ f_{i,j,k,l}(2-l) \] -
与边界相邻,且与连通块相邻 \((j\ge 1)\)
此时一边是小的,贡献为\(i\),同理
\[f_{i+1,j,k+i,l+1}\leftarrow^+ f_{i,j,k,l}(2-l) \]
最后概率就是\(\times \frac{1}{n!}\)
CF1821F Timber
归类:组合计数、容斥
每棵树会占据一个长度为 \(k+1\) 的区间,我们先考虑将这些区间放到 \([1,n]\) 中,也即,我们要在 \([1,n]\) 中放 \(m\) 个长度为 \(k+1\) 的区间。我们钦定一个区间的左端点为代表元,那么可以在放好代表元之后再把剩下的 \(k\) 个依次插到后面。所以可以放代表元的位置就有 \(n-mk\) 个,因此方案数就是 \(\binom{n-mk}{m}\)。
确定好每棵树占据的区间,我们可以让这棵树向左倒/向右倒,所以每个有两种选择,选择方案数是 \(2^m\),于是我们很naive地认为总方案数是 \(\binom{n-mk}{m}\times 2^m\)。
这是错的,因为可能有树既能往左倒也能往右倒,这在原来的统计方法中会被算 \(2\) 次,而题目中要求的是不同的种树方案,不同的倒下方案是不被统计的。
所以我们考虑容斥,减去既能往左倒也能往右倒的方案数。假设有 \(i\) 棵树可以往左和往右倒,我们可以将这棵树看做同时往左右倒,于是其占据了 \([x-k,x+k]\) 的位置,于是这棵树的区间比原来多 \(k\) 个占位,因此空位就有 \(n-mk-ik=n-(m+i)k\) 个,放区间方案数是 \(\binom{n-(m+i)k}{m}\)。这 \(i\) 棵树不能选择左右,对于其他的 \(m-i\) 棵树能选择左右,所以方案数为 \(2^{m-i}\)。从 \(m\) 个选 \(i\) 个,\(\binom{m}{i}\)。因此总方案数就是:
考虑容斥系数,本质是一个求并集的思想,所以容斥系数是 \((-1)^{i-1}\)。
因此答案为:
P3978 [TJOI2015] 概率论
归类:组合计数
令 \(f_n\) 表示 \(n\) 个点二叉树的个数,\(g_n\) 表示 \(n\) 个点二叉树的叶子个数和。答案就是 \(\frac{g_n}{f_n}\)
叶子个数和不好求,我们考虑将【树的形态和其每个叶子】看做一个配对,那么 \(g\) 实际就是这个配对的数量。
考虑增量构造。假设现在有一个 \((n-1)\) 个点的二叉树,如果给这个二叉树加入一个点,并且这个点变成叶子结点,那么这棵新的树就可以和新增的叶子组成一个配对。\((n-1)\) 个点的二叉树总共有 \(2(n-1)\) 个槽,有 \((n-2)\) 个位置已经有点了,因此有 \(2(n-1)-(n-2)=n\) 个地方可以放叶子。所以总配对数量就是 \(f_{n-1}\times n\)。
因此我们得到重要结论: \(g_n=f_{n-1}\times n\)。
根据经典结论,\(f_n=\mathrm{Cat}_n=\frac{\binom{2n}{n}}{n+1}\)
所以
CF1523E
归类:组合计数、概率期望
看到贡献为 \(P(i)\times i\),用经典转化,定义 \(p_i\) 表示在 \(i\) 个台灯被点亮时仍然存活的概率,答案就是 \(\displaystyle\sum_{i=1}^{n} p_i+1\)。
点亮 \(i\) 个台灯的总方案数就是 \(\displaystyle\binom{n}{i}\)。考虑怎么对合法方案计数,不难发现此时的必要条件为任意两个点亮的台灯之间至少有 \((k-1)\) 的间隔。
有这样几种计数方法:
-
把亮的台灯之间加入灭台灯看作放抽屉,就是
...o...o...o...
,先在中间的 \((i-1)\) 个空中每个放入 \((k-1)\) 个灭台灯作为保底,这样可以保证合法了,还剩下 \(n-(i-1)(k-1)\) 个灭台灯,放入 \((i+1)\) 个空位,插板法即可。 -
把每个亮台灯和其后面放的 \((k-1)\) 个灭台灯捆绑起来,这样考虑:先放好所有的亮台灯,然后再把后面的 \((k-1)\) 个灭台灯插到后面。为了保证放得下,亮台灯有 \(n-(i-1)(k-1)\) 个位置选择,方案数就是 \(\displaystyle\binom{n-(i-1)(k-1)}{i}\)
所以我们得到 \(p_i=\frac{\binom{n-(i-1)(k-1)}{i}}{\binom{n}{i}}\),直接计算,时间复杂度 \(O\left(\frac{n}{k}\right)\)
CF2119D
[AGC061C] First Come First Serve
归类:计数 dp、容斥
每个人可以选择 \(a_i\) 或 \(b_i\),并且选择时间点确定则相对顺序唯一确定,因此总方案数是 \(2^n\)。考虑这样统计会有什么问题:
-----
--- ----
此时中间的区间选择 \(a_i\) 和 \(b_i\) 是等价的,对于相对顺序没有影响,会算重,观察发现,选 \(a_i,b_i\) 等价,等价于 \([a_i,b_i]\) 没有其他时间点被选择。考虑容斥 dp,定义 \(f_i\) 表示前 \(i\) 个区间的相对位置方案数,显然第 \(i\) 个区间有两种选择,即 \(f_i\leftarrow 2f_{i-1}\),接着考虑容斥掉 \([a_i,b_i]\)。对于 \([a_i,b_i]\) 有影响的区间就是与其有交的,显然这是一段连续的,令 \(j,k\) 为最大的 \(b_j<a_i\) 和 \(a_k<b_i\),则 \((j,k]\) 就是与 \([a_i,b_i]\) 有交的区间,由于区间没有包含关系,因此钦定这些区间都选到 \([a_i,b_i]\) 外面,即可使 \([a_i,b_i]\) 中的时间点为空,本质是令 \((j,k]\) 只有一种选择,这种情况下 \([a_i,b_i]\) 选在 \(a_i,b_i\) 是一样的,需要容斥掉,因此有转移:\(f_k\leftarrow^- f_j\),其含义是:在 \(k\) 处,有区间 \((j,k]\) 的一种选法使得 \(a_i\) 被算重,因此减去 \(f_j\) 以去重。而 \(j,k\) 可以双指针求出,时间复杂度 \(O(n)\)。
P4099 [HEOI2013] SAO
归类:计数 dp、树上背包
外向树拓扑序计数:
\(n\) 个点的外向树拓扑序个数为:\(\dfrac{n!}{\displaystyle\prod sz_i}\)。
证明:
从概率的角度考虑,即随机一个顺序是合法拓扑序的概率。充要条件就是每棵子树的根都在其内部点的前面,将其拎出来单独考虑,相当于钦定最靠前的是根,随机情况下概率就是 \(\dfrac{1}{sz_i}\),算在一起就是上式。
于是外向树的情况可以直接用子树大小计算,但是加入内向边就不好算了,此处考虑容斥。假设保留内向边集 \(S\) 内的边,并钦定这些边都不满足,即钦定这个边集中所有内向边都被改成外向边,且其他的内向边都被断掉。记其方案数为 \(F(S)\),则乘上容斥系数 \((-1)^{|S|}\) 后,答案就是 \(\displaystyle\sum_{S}(-1)^{|S|} F(S)\)。
因此考虑一个树形 \(\mathrm{dp}\),定义 \(g_{u,i,j}\) 表示考虑 \(u\) 内的所有子树,包含 \(u\) 的连通块大小为 \(i\),钦定了 \(j\) 条内向边的方案数,最后方案数就是 \(\displaystyle\sum_{j} (-1)^j g_{u,i,j}\)。但是这个复杂度不对。不过注意到每次最多添加 \(1\) 条,所以可以将 \((-1)^j\) 这个容斥系数放进 \(\mathrm{dp}\) 里,定义 \(f_{u,i}\) 表示考虑 \(u\) 内的所有子树,包含 \(u\) 的连通块大小为 \(i\),乘上容斥系数的方案数。
考虑背包转移,对于一条边 \((u,v)\):
现在已经有长度为 \(sz_u\) 的拓扑序,可以按顺序随便插 \(sz_v\) 个,根据根据多重集的组合数是 \(cof=\dfrac{(sz_u+sz_v)!}{(sz_u)!(sz_v)!}\)。
-
\((u,v)\) 为外向边
没有任何特殊,直接并上一个 \(v\) 内部大小为 \(j\) 的连通块,因此得到转移:
\[f^{\prime}_{u,i+j}\leftarrow \mathrm{cof}\times f_{u,i}f_{v,j} \] -
\((u,v)\) 为内向边
-
不把 \((u,v)\) 选入 \(S\) 中
那么这条边相当于被断掉了,对联通块个数没有贡献:
\[f^{\prime}_{u,i}\leftarrow \mathrm{cof}\times f_{u,i}f_{v,j} \] -
把 \((u,v)\) 选入 \(S\) 中
类似转移,不过此时乘上 \((-1)\) 的容斥系数:
\[f^{\prime}_{u,i+j}\leftarrow (-1)\times \mathrm{cof}\times f_{u,i}f_{v,j} \]
-
最后背包完后,根据外向树的方案数,要乘上 \(u\) 所在连通块的 \(\dfrac{1}{sz_i}\),即 \(f_{u,i}\leftarrow f_{u,i}\times \dfrac{1}{i}\)。
P13275 [NOI2025] 集合
[ARC120F] Wine Thief
[AGC012F] Prefix Median
分类:计数 dp
看到本质不同的 \(b\) 序列个数,转化为类似判定的问题,看看如何刻画一个序列 \(b\) 有办法合法,一般是挖掘要使 \(b\) 合法的等价(充要)条件。由于是中位数的问题,首先对 \(a\) 排序。
首先可以挖掘出一个比较显然的必要条件:\(b_i\in a_{[i,2n-i]}\)。证明:不妨设 \(b_i=a_j\),那么 \(j\) 左边和 \(j\) 右边要恰好选出 \((i-1)\) 个数,于是最左选到 \(i\),最右选到 \(2n-i\),因此 \(j\in [i,2n-i]\)。
然后有一个 \(\texttt{observation}\):\(b_i\to b_{i+1}\),每次可选的数集会增加两个数,那么原来的中位数只可能【不变/变成前驱/变成后继】,等价于任意两个 \(b_i,b_{i+1}\) 之间没有夹着任何数,也即需要满足 \(\forall j,b_j\not\in (\min(b_i,b_{i+1}),\max(b_i,b_{i+1}))\)。
我们声称,满足这两个条件就是充要的了。于是只需要对满足上述两个条件的 \(b\) 计数。
考虑定义 \(f_{i,l,r}\) 表示填好了后 \(i\) 个值,即确定了 \(b_{[n-i+1,n]}\),并且此时当前选择的位置左边还有 \(l\) 个空可以填,\(n\) 右边还有 \(r\) 个空可以填。考虑 \(b_{i+1}\rightarrow b_i\) 的方法,由于要有夹在中间的限制,讨论转移:
-
\(b_{i+1}=b_i\)
此时中间没有限制,左边右边分别扩展一个空,得到 \(f_{i,x+1,y+1}\leftarrow f_{i+1,x,y}\)。
-
\(b_{i+1}>b_i\)
就是将 \(b_i\) 放在左边,限制 \((b_i,b_{i+1})\) 中不能填了,枚举这段区间用到的数的个数 \(j\),那么 \(b_i\) 左边就剩下 \((x+1-j)\) 个位置,右边扩展了一个空,并且 \(y\) 统计的是右边的个数,不包括 \(b_{i+1}\),此处加上 \(b_{i+1}\),个数变成 \((y+2)\),得到 \(f_{i,x+1-j,y+2}\leftarrow f_{i+1,x,y}\)。
-
\(b_{i+1}<b_i\)
同理,得到 \(f_{i,x+2,y+1-j}\leftarrow f_{i,x,y}\)。
结束了吗?注意到 \(a_i\) 相同会造成答案算重,因为算出来的 \(b\) 是本质相同的。这种情况的去重方法就是考虑将值相同的 \(a_i\) 用位置区分开,于是在扩展时钦定相同的数只在第一次出现时统计,其他出现位置钦定不选即可。
时间复杂度 \(O(n^4)\)。
CF53E Dead Ends
分类:计数 dp,状压 dp,去重
\(n\le 10\),考虑状压,如何设计状态?最直接的是 \(f_{S,k}\) 表示点集为 \(S\) 的生成树中叶子个数为 \(k\) 的方案数,不过我们发现这无法转移,因为接上的点可能会改变之前的叶子状态,于是改成记录叶子集合,\(f_{S,T}\) 表示叶子集合为 \(T\) 的方案数,即可每次加入一个点转移,得到:
其中 \(\mathrm{trans}(T,v,u)\) 表示在 \(S\) 中的点 \(v\) 处接上一个点 \(u\) 后得到的新叶子集合。如果 \(v\in T\),接上 \(u\) 之后就不是叶子了,结果为 \(T\setminus \set{v}\cup \set{u}\),否则不受影响,结果为 \(T\cup \set{u}\)。
但这是错的,因为和多数状压图计数一样,这样会算重,不难发现问题是叶子间接入的顺序是无关的,但在我们的统计中每增加一个叶子会被恰好计算 \(|T|\) 次,因此只需要用 \(f_{S,T}\times \dfrac{1}{|T|}\) 作为正确结果转移即可。也可以钦定顺序,钦定每次增加的叶子编号要递增即可。
时间复杂度 \(O(3^n n^2)\),可以通过。
P8340 [AHOI2022] 山河重整
归类:容斥、背包
CF2134F Permutation Oddness
归类:组合计数、计数 dp
\(\mathrm{DP}\)
P5280 [ZJOI2019] 线段树
归类:特殊 dp,概率期望
每次复制线段树的操作不好处理,但是线段树的形态是固定的,所以可以考虑求线段树上每个节点的贡献。
定义\(f_u\)表示线段树上点\(u\)的贡献,具体的,\(f_u\)表示在所有线段树中\(u\)处的\(\mathrm{tag}\)的期望值。
然后对于一次\(\mathrm{Modify}(l,r)\)操作涉及到的点分类讨论
-
被完全覆盖的点(被打上此轮标记的点)
这个点的\(\mathrm{tag}=1\)。当前的线段树会分成两份,其中一半会打上标记,所以得到转移:
\(f_u\leftarrow \frac{1}{2}(f_u+1)\)
-
被半覆盖且访问到的点(被主动\(\mathrm{pushdown}\)的点)
这个点被执行了\(\mathrm{pushdown}\),所以肯定有\(\mathrm{tag}=0\),同理得到
\(f_u\leftarrow \frac{1}{2}f_u\)
-
完全没有被覆盖的点(可能被半覆盖的点下放到的点)
这个点现在无法确定状态,因为不知道其祖先中是否有\(\mathrm{tag}\)会被下传到这个点。
所以我们还需要一个辅助\(g_u\),表示在下传时,点\(u\)的祖先中有\(\mathrm{tag}\)的概率。继续分讨计算\(g\):
-
被完全覆盖的点(被打上此轮标记的点)
其\(\mathrm{tag}=1\),因此其子树中的所有点都有
\(g_{v}\leftarrow \frac{1}{2}(g_{v}+1)\)
-
被半覆盖且访问到的点(被主动\(\mathrm{pushdown}\)的点)
其祖先的\(\mathrm{tag}\)一定已经下放,因此
\(g_u\leftarrow \frac{1}{2}g_u\)
-
完全没有被覆盖的点(可能被半覆盖的点下放到的点)
取决于其祖先中是否存在有\(\mathrm{tag}\)的点被下放,如果有,那么就会堆在\(u\)处,因此\(g\)不变,\(f\)有变化:
\(f_u\leftarrow \frac{1}{2}(f_u+g_u)\)
于是dp部分就完成了,\(f,g\)可以在遍历时修改。
注意到\(g\)需要在整棵子树中操作,所以需要打标记,若某个子树内需要进行\(p\)次子树操作,即\(x\leftarrow \frac{1}{2}(x+1)\),不难得到这个相当于\(x\leftarrow \frac{1}{2^p}x+\frac{2^p-1}{2^p}\),可以打标记实现。
最后每个点的贡献就是\(f_u\times cnt\),答案为\(\sum f_u\times cnt\)
P3592 [POI 2015] MYJ
归类:区间 \(\mathrm{DP}\)
关于最大/最小值的限制,考虑笛卡尔树,定义 \(f_{l,r,x}\) 表示考虑 \([l,r]\) 的洗衣店,其中价格的最小值 \(\ge x\) 的最大获利。可以不选 \(x\),\(f_{l,r,x}\leftarrow f_{l,r,x+1}\);选 \(x\),则用笛卡尔树的方法,直接枚举最小值 \(x\) 的位置 \(k\),考虑 \(x\) 目前会贡献到的洗衣店,其满足 \(l\le a_i\le k\le b_i\le r\),这些洗衣店都付 \(x\) 元,所以贡献为 \(cnt\times x\),得到转移: \(f_{l,r,x}\leftarrow \displaystyle\max_{l\le k\le r} \set{f_{l,k-1,x}+f_{k+1,r,x}+cnt\times x}\)
由于不关心 \(c_i\) 具体大小,可以离散化,\(x\) 一维大小是 \(O(m)\),\(cnt\) 可以做的时候预处理,时间复杂度 \(O(n^3m)\)
P13272 [NOI2025] 序列变换
P3447 [POI 2006] KRY-Crystals
CF1987F2 Interesting Problem (Hard Version)
分类:区间 \(\mathrm{dp}\)
删除数的过程不好刻画,不过我们可以考虑 \(a_i\) 和后面与其一起删除的数 \(a_j\),那么过程只能是 \(a_{[i+1,j-1]}\) 都被删空了,然后 \((a_i,a_j)\) 操作一次删除。这启发我们设计这样一个 \(\mathrm{dp}\),定义 \(f_{l,r}\) 表示要删空 \([l,r]\) 区间,至少要在 \([1,l-1]\) 中删除多少个数。转移考虑枚举与 \(a_l\) 一起删除的数 \(a_k,k\in [l,r]\),首先 \(a_l\) 要能够进行删除操作,这意味着要在 \([1,l-1]\) 中删除一些数,使得 \(a_l\) 对上其下标,不难发现前面需要删掉 \(l-a_l\) 个数,那么如果 \(l<a_l\vee l\not\equiv a_l\pmod 2\) 就寄了,否则就需要操作 \(v=\dfrac{l-a_l}{2}\) 次。然后我们需要删掉 \(a_{[l+1,k-1]}\),自然需要满足 \(l+1\equiv k-1\pmod 2\),并且由于 \(a_l,a_k\) 同时删除,所以在删除 \([l+1,k-1]\) 时前面恰好进行 \(v\) 次操作,否则 \(a_l\) 最终无法被删除,因此需要满足 \(f_{l+1,k-1}\le v\),删除这一段的操作次数就是 \(\dfrac{k-l+1}{2}\),剩下的一部分 \([k+1,r]\),要求 \([1,k]\) 的操作次数就是 \(f_{k+1,r}\),不过由于 \([l,k]\) 已经被删空了,所以只需要 \(f_{k+1,r}-\dfrac{k-l+1}{2}\) 次操作,得到最终的转移:\(f_{l,r}\leftarrow \max\left(v,f_{k+1,r}-\dfrac{k-l+1}{2}\right)\)。
考虑最后如何求出答案。再用一个 \(\mathrm{dp}\),定义 \(g_i\) 表示 \([1,i]\) 的最多操作次数,可以不考虑 \(i\),有 \(g_i\leftarrow g_{i-1}\)。转移自然是考虑删掉一段区间 \([j,i]\),能删掉的条件就是 \(g_{j-1}\ge f_{j,i}\),有 \(g_i\leftarrow g_{j-1}+\dfrac{i-j+1}{2}\)。
时间复杂度瓶颈在区间 \(\mathrm{dp}\),是 \(O(n^3)\) 的,不过因为转移时 \(k\) 只会枚举同奇偶,所以带小常数,可以通过。
[ARC130E] Increasing Minimum
分类:贪心
每次操作的限制就是全局 \(\min\),考虑将全局 \(\min\) 相同的操作划分为一段,即每段中操作的 \(A_{b_k}\) 都是相同的,这样,每段操作完成后,全局 \(\min\) 就会 \(+1\)。挖掘一下每段的性质,每次只能操作等于全局 \(\min\) 的数,所以每段中 \(b_k\) 都是互不相同的,并且除了最后一段以外,后一段的操作集合一定包含前一段的操作集合,因为等于最小值的集合只会扩张。不难发现满足这两个条件的划分方案就是合法的。考虑如何求出字典序最小的方案。
全局 \(\min\) 肯定是 \(1,2,\dots\) 往上加,那么从小到大每个数都尽可能多的填即可做到字典序最小。定义 \(f_i\) 表示在最优方案下划分 \(b_{[1,i]}\) 当前的的全局 \(\min\)。转移时,考虑 \([j,i]\) 表示 \(A_{b_{[j,i]}}\) 划分到同一段,那么限制就是 \(b_{[j,i]}\) 互不相同,并且 \(b_{[1,j)}\subseteq b_{[j,i]}\),此时有转移 \(f_i\leftarrow^{\min} f_j+1\),不过注意到可以直接取到 \(j\) 的最小值转移,即 \(f_i\leftarrow f_{j_{\min}}+1\),\(j_{\min}\) 可以简单求出。可以贪心找到最优的最后一段,然后逆推整个过程构出答案。
P3643 [APIO2016] 划艇
分类:dp优化、组合数学
定义 \(f_{i,j}\) 表示考虑前 \(i\) 所学校,且当前所有派出的个数最大值为 \(j\) 的方案数,分 \(i\) 派出和不派出,可以得到转移:
状态数是 \(O(nV)\) 的,不能接受。此处压值域只能考虑离散化了,转变定义,\(j\) 表示最大值落在 \([pos_j,pos_{j+1})\) 区间。这样会得到转移:
但这是错的,因为我们没有考虑同一个区间内的情况,何意味?用 \(\ell\) 表示区间长度,假设有 \(x\) 个数落在这个区间中,那么按顺序填完的方案数是 \(\dbinom{\ell}{x}\),而非单纯的 \((pos_{j+1}-pos_j)\) 转移。所以我们再记录一个有几个数落在 \(j\) 区间中,用 \(f_{i,j,k}\) 表示考虑前 \(i\) 所学校,当前派出的个数最大值落在 \([pos_j,pos_{j+1})\),且这个区间中已经填了 \(k\) 个数。那么可以不派出 \(i\),得到 \(f_{i,j}\leftarrow f_{i-1,j}\);如果派出 \(i\),可以继续分到这个区间中,原来的贡献系数是 \(\dbinom{\ell}{k-1}\),现在加一个变成 \(\dbinom{\ell}{k}\),相当于 \(\times \dfrac{\ell-k+1}{k}\);也可以新开一个区间,在新的区间内有 \(\ell\) 种选法。综上,得到:
使用前缀和简单优化一下,时间复杂度 \(O(n^3)\)。
UOJ【UR #13】Ernd
归类:二维偏序、斜率优化
先考虑暴力的 \(\mathrm{dp}\)。贡献与每一个连续段长度有关,分段进行 \(\mathrm{dp}\),定义 \(f_i\) 表示在必选第 \(i\) 个水果时的最大收益。转移时枚举区间 \([j,i]\) 为极长的连续段,则再另 \(g_i\) 表示一定不选第 \(i\) 个水果时的最大收益,得到转移:
可以做到 \(O(n^2)\)。需要优化。
先优化 \(g_i\) 的计算,\(j\rightsquigarrow i\) 等价于 \(|a_i-a_j|\le b_i-b_j\),考虑如何拆掉这个绝对值,如果按 \(a_i,a_j\) 大小关系分讨就不好处理,但注意到我们有 \(|a_i-a_j|=\max(a_i-a_j,a_j-a_i)\le b_i-b_j\),因此可以拆成两个限制:
看似我们将其做到了一个二维偏序的形式,但是还有 \(j\le i\) 的限制。不过再次注意到,由于 \(|a_i-a_j|\ge 0\),所以有 \(b_i-b_j\ge |a_i-a_j|\ge 0\),即 \(b_i\ge b_j\Rightarrow j\le i\)。所以只是一个二维偏序,因此可以扫描线+树状数组解决。
现在需要优化 \(f_i\) 由 \(g_j+(i-j+1)^2\) 的转移,由于是取 \(\max\),所以这个东西是可以斜率优化的,但是还有 \(\mathrm{pass}(j,i)\) 的限制。不过注意到 \(\mathrm{pass}([l,r])\) 实际构成了一个个连续段,因此每个 \(i\) 只会从一个连续段中转移,对当前扫到的连续段维护凸包转移即可。
时间复杂度 \(O(n\log n)\)。
CF1773G Game of Questions
归类:状压 dp,概率期望
[AGC013E] Placing Squares
厉害题。
代数(\(\mathrm{dp}\))做法
不难想到一个 trivial 的 \(\mathrm{dp}\),\(f_i\) 表示考虑前 \(i\) 个格子放正方形的总贡献,令 \(S=\set{a_i}\),容易得到转移 \(f_i=\displaystyle\sum_{j<i\wedge j\not\in S} f_j\times (i-j)^2\),为了简化转移,等价变换为 \(f_i = [i\in S]\times \displaystyle\sum_{j<i} f_j(i-j)^2\)。
可以简单优化到时间复杂度 \(O(V)\),无法通过。注意到如果没有 \(S\) 的限制,即相邻两个 \(a_i\) 之间的部分,其转移都是固定的 \(\sum f_j\times (i-j)^2\) 形式,考虑快速计算这部分,不难想到矩乘,套路地寻找 \(f_i\) 和 \(f_{i+1}\) 之间的关系,直接展开 \(f_{i+1}\),得到:
于是我们需要找出 \((a_i,b_i,c_i)\to (a_{i+1},b_{i+1},c_{i+1})\) 之间的递推关系。不难发现 \(a_i=f_i\),以此为基准:
如果限定 \(f_i=0\) 也同理,不难得到 \(a_{i+1}=a_i+2b_i+c_i,b_{i+1}=b_i+c_i,c_{i+1}=c_i\)。
于是我们发现上述递推式都可以用矩阵 \([a_i,b_i,c_i]\) 表示,转移矩阵对应:
其含义为在不限定 \(f_i=0\),即 \(i\not\in S\) 时转移矩阵为 \(A\),否则转移矩阵为 \(B\)。因此可以在 \((a_i,a_{i+1})\) 之间用矩阵快速幂乘上 \(A\) 的幂,\(a_i\) 处乘上 \(B\) 即可。时间复杂度 \(O({\omega}^3n\log V)\),可以通过。不过由于每次乘上的都是固定的矩阵 \(A\),所以可以预处理 \(A^{2^k}\),询问时由于矩阵乘有结合律,可以二进制拆分后一个一个往后乘,一维矩阵乘法在每一行本质是一个矢量乘向量,可以做到 \(O({\omega}^2)\),于是可以 \(O({\omega}^3\log V)\) 预处理,\(O({\omega}^2\log V)\) 做单次,时间复杂度 \(O\left({\omega}^3 \log V+n({\omega}^2\log V+{\omega}^3)\right)\),不过由于 \(\omega=3\) 过小,没有什么优化价值。
组合意义做法
可以将放满正方形看做用隔板隔开空间,套路地,考虑平方的组合意义,可以看做每段中放两个颜色不同的球的方案数,其就是 \({\ell}^2\),所以可以对每一段设计这样的 \(\mathrm{dp}\),\(f_{i,0/1/2}\) 表示考虑前 \(i\) 个位置,这段已经放置了 \(0/1/2\) 个小球的方案数,转移是简单的此处不赘述,其同样可以用矩阵快速幂优化,时间复杂度相同。
P12196 [NOISG 2025 Prelim] Lasers 2
考虑刻画出合法方案的形态和充要条件,再用 \(\mathrm{dp}\) 来最优化。
最终锁覆盖的一定是若干个不交的非空极长连续段,记这些连续段为 \([L_1,R_1],\dots,[L_k,R_k]\),其合法的充要条件是什么?限制最强的是最长的锁,其他的锁都可以缩到这个锁的区间里,记其长度为 \(\ell=\max\set{r_i-l_i+1}\),则合法等价于存在 \(R_i-L_i+1\ge \ell\)。并且还有代价的限制,直接追踪哪些区间解锁显然不好做,正难则反,考虑有哪些区间不需要解锁,由于各个连续段不交,所以可以对每个连续段统计,记 \(w(l,r)\) 表示满足 \([l_i,r_i]\in [l,r]\) 的锁 \(c_i\) 之和,则不需要动的锁代价和为 \(\sum w(L_i,R_i)\),需要动的锁代价和自然就是 \(\sum c_i-\sum w(L_i,R_i)\)。综上,我们可以得到连续段方案合法等价于:
接下来就是用 \(\mathrm{dp}\) 来求解。有两种方向,可以考虑固定使用代价下的最大空位,也可以考虑固定空位下的最少代价,由于代价范围过大,因此选取后者,在固定空位数量下对最少代价 \(\mathrm{dp}\),代价可以看做最大化 \(\sum w(L_i,R_i)\),因此设计状态 \(f_{i,j,0/1}\) 表示考虑前 \(i\) 列,有 \(j\) 列空着,目前是否有 \(R_i-L_i+1\ge \ell\) 的连续段出现,此时 \(\sum w(L_i,R_i)\) 的最大值。转移分第 \(i\) 列的状态讨论:
-
第 \(i\) 列留空 \(f_{i,j,o}\leftarrow f_{i-1,j-1,o}\)。
-
第 \(i\) 列作为某个连续段右端点出现 \(f_{i,j,o\vee [i-l+1\ge \ell]}\leftarrow f_{l-1,j,o}+w(l,i)\)
直接暴力枚举 \(l\) 转移时间复杂度 \(O(m^3)\),无法通过。\(j,o\) 处的变化可以简单处理,转移的本质是 \(f_i=\max \set{f_{l-1}+w(l,i)}\),考虑对 \(i\) 扫描线时在线段树上对每个 \(l\) 动态维护 \(w(l,i)\),具体的加入 \(r=i\) 的区间并线段树上区间加,维护 \(\max\) 即可,此处不再赘述。
时间复杂度 \(O(m^2\log m)\)。
[WC2021] 表达式求值
直接整个数组作为主体来考虑显然是不好的,由于都是求和所以可以直接对每列拆贡献,看做求 \(n\) 次 \(m\) 个变量的总和,考虑第 \(k\) 列,将 \(A_k\) 记为 \([a_0,a_1,\dots ,a_{m-1}]\)。由于牵扯到大小关系且值域较大,继续拆,考虑每个变量 \(a_i\) 作为答案的方案数。原表达式中的 \(0\sim 9,\texttt{?}\) 可以看做是 \(a\) 中的一个数,套路地建立表达式树并考虑 \(\mathrm{dp}\) 出方案数,定义 \(f_{u,0/1}\) 表示考虑表达式树的 \(u\) 子树内部,目前得到的结果是 \(<a_i\) 还是 \(\ge a_i\) 的方案数,转移是简单的。但是重新审视复杂度,这样每做一列都要枚举每个数、遍历一遍表达式树,时间复杂度其实是 \(O(nm|E|)\) 的,退化成暴力了。怎么会是呢?
我们注意到每一列的 \(\mathrm{dp}\) 过程实际上是很相似的,这样对每一列都跑一遍就浪费了很多信息,具体来挖掘一下,我们发现其实每次的具体数值是不重要的,大小才是有用的,决定某个 \(a_i\) 方案数的因素只有 \(a\) 中比 \(a_i\) 小的集合。我们发现这样可以做了,定义 \(f_{S,u,0/1}\) 表示钦定比 \(a_i\) 小的集合为 \(S\),考虑表达式树的 \(u\) 子树内部,目前得到的结果是 \(<a_i\) 还是 \(\ge a_i\) 的方案数,转移分讨一下:
-
若 \(u\) 是叶子,对应下标 \(p\)。 那么若 \(p\in S\) 则结果要小于 \(a_i\),\(f_{S,u,0}=1\),否则 \(f_{S,u,1}=1\)。
-
若 \(s_u=\texttt{<}\) 或 \(\texttt{>}\),对应 \(\oplus=\min,\max\) 操作
\[f_{S,u,\oplus(i,j)}\leftarrow f_{S,lc,i}\times f_{S,rc,j} \] -
若 \(s_u=\texttt{?}\),对应 \(\min,\max\) 操作方案数的和。
因此可以 \(O(2^m|E|)\) 做完这个 \(\mathrm{dp}\)。考虑如何求答案。
对 \(a\) 排序后,记 \(S=\set{p_1,\dots,p_{i-1}}\),则 \(\ge a_i\) 的方案数 \(F(i)=f_{S,rt,1}\)。但是我们想要 \(=a_i\) 的方案数,这个简单,直接容斥掉即可,即 \(=a_i\) 的方案数为 \(F(i)-F(i+1)\),因此贡献就是 \(\displaystyle\sum_{i\in [0,m)}[F(i)-F(i+1)]\times a_i\)。求个和就得到答案,总时间复杂度 \(O(2^m |E|+nm)\)。
体现了一种信息重复利用(?)的思想。
图论/树论
DFN序
对于单点加\(w\),其子树中每个点到根的距离都加\(+w\),转化成\(\mathrm{dfs}\)序上一段区间的加,可以用树状数组维护
对于子树加\(w\),其子树中每个点都加上\((dep_y-dep_x+1)w=(1-dep_x)w+dep_yw\),开两个树状数组维护
对于查询,先用\(O(n\log n)-O(1)\)的\(\mathrm{LCA}\),然后差分一下求距离即可。
因此总时间复杂度可以做到\(O(n\log n)\)
P5290 [十二省联考 2019] 春节十二响
既然祖先-后代关系不能放到同一个块,那么考虑自下而上处理,每次贪心地将子树中可以合并的合并,不难发现可以维护大根堆,子树合并时取出堆顶取\(\max\)合并
P6845 [CEOI 2019] Dynamic Diameter
直径有三种分析方向:
- 两次dfs。对于此题明显不适用。
- dp求。因为要动态维护,所以也不好处理。
- 暴力表示出来。虽然看着很劣但是在维护时可以优化,所以此题选用这个方法。
考虑直径的本质,就是距离最大的两个点:
这个\(lca(x,y)\)很难处理,但是在\(lca\)的\(O(1)\)欧拉序求法中,我们可以用【 区间\([\min(L_x,L_y),\max(R_x,R_y)]\)中深度最浅的点 】来表示\(x,y\)的\(lca\),其中\(L,R\)表示这个点在欧拉序中前后分别出现的位置。
所以原来的式子可以转化成:
考虑在欧拉序上枚举两个点,那么原式就变成:
这个可以用线段树维护,考虑将左右两段拼起来,分最小值在左边、右边讨论即可。
而边的修改可以看做是子树\(d\)整体加,那么在欧拉序上体现为区间加,打\(\mathrm{tag}\)实现。
P5666 [CSP-S2019] 树的重心
枚举割哪条边然后求重心不好处理,考虑拆贡献,具体的,我们考虑每个点会在哪些边被割掉时作为重心。
原式中有两部分,其中子树外的部分很难处理,但是重心有性质:【如果我们以重心为根\(rt\),那么一个不是根的点成为重心所割掉的边一定不在其子树内】。可以考虑反证,因为割掉之后往子树里走显然是不优的。
先考虑\(x\ne rt\)。假设把树割掉了\(S\)(即\(x\)不在的部分的大小):
记\(g_x=\max_{v\in son(x)}\set{sz_v}\),则满足割掉的边不在其子树内的点\(x\)可以作为重心等价于:
所以我们要对每个\(x\)统计有多少个不在其子树内的\(S\)在指定区间内。可以先全局统计,然后再减去子树内的。注意\(S\)的取值有\(sz\)和\(n-sz\)两种情况,树状数组维护。
然后对于\(x=rt\),不难发现其能保留在重心的位置,当且仅当割掉一个子树后,根的每个儿子大小都满足要求。设\(rt\)的重儿子为\(u\),次重儿子为\(v\)。
如果割掉的子树在\(u\)的子树中,那么\(u\)肯定满足条件,此时判断次重儿子\(v\)是否满足条件,即
如果割掉的不在\(u\)子树内,那么判断\(u\)是否满足条件
同样可以简单求。
P6118 [JOI 2019 Final] 独特的城市 / Unique Cities
P7215 [JOISC 2020] 首都
归类:点分治
先考虑暴力怎么做。枚举最终首都的颜色,随便找一个点作为根 \(rt\),尝试将所有颜色相同的点都与 \(rt\) 连通。考虑开一个队列,表示当前要与 \(rt\) 连通的点,取出队头,尝试更新其父亲来连通,如果其父亲的颜色已经被合并,那么直接将其父亲入队,否则需要打通这个颜色,将所有这个颜色的点都入队即可。
由于要不断更换根,考虑点分治。一个看似错误的做法是每次以分治中心作为根,将其子树中的点并上来。但这样分治中心外的同色点就没办法被统计到。但我们发现此时可以直接舍弃这个分治中心,因为在更高层的分治层被扩展到的点肯定不会比当前分治中心为根扩展到的点多,所以以当前点为根是不优的,直接跳过。时间复杂度 \(O(n\log n)\)
CF840E In a Trap
归类:树分块
\(\max\set{a_i\oplus \mathrm{dist}(i,v)}\) 这样的式子常规数据结构不好维护,并且注意到数据范围 \(n\le 5\times 10^4\),那么直接考虑暴力分块。由于涉及到路径长度和异或,我们将 \(u\to v\) 这条链每 \(2^8=256\) 个点分一个块,从下到上从 \(0\) 开始编号,第 \(i\) 块涉及到 \([256i,256(i+1)-1]\) 级祖先。
回到原问题,我们考虑一条从 \(x\) 向上的链的第 \(i\) 块,记这一块的底部点,即 \(x\) 的第 \(256i\) 级祖先为 \(u\),那么考虑令 \(f_{u,i}\) 表示这一段的答案,可以表示出 \(f_{u,i}=\displaystyle\max_{j=0}^{255}\set{a_{\mathrm{fa}_{u,j}} \oplus (256i+j)}\)。由于在 \(2^8\) 意义下进行分块,我们将每个数拆成高8位和低8位,令 \(high_i=(a_i\texttt{>>}8),low_i=(a_i\& 255)\),则 \(a_i=256high_i+low_i\)。
原式可以分开计算:
考虑如何对于 \(u\) 快速求出所有 \(f_{u,i}\)。观察这个 \(\max\),对于每个 \(i\) 我们相当于要在 \((high_{\mathrm{fa}_{u,j}}\oplus i)\) 取到最大时 \((low_{\mathrm{fa}_{u,j}}\oplus j)\) 取到最大,可以将所有 \(high_{\mathrm{fa}_{u,j}}\) 挂到 \(\texttt{01-Trie}\) 上,即可查询到使得 \((x\oplus i)\) 的最大 \(x\),然后再对所有 \(high_{\mathrm{fa}_{u,j}}=x\) 的 \(j\) 找到最大的 \((low_{\mathrm{fa}_{u,j}}\oplus j)\) 即可,可以开一个桶维护。由于 \(i\) 只有 \(O(\sqrt{n})\) 级别,\(\texttt{01-Trie}\) 上的查询是 \(O(\log n)\),所以可以 \(O(n\sqrt{n}\log n)\) 处理出所有的 \(f_{u,i}\)。
查询时,只需要一块一块往上跳,时间复杂度 \(O(m\sqrt{n})\)。总时间复杂度 \(O(n\sqrt{n}\log n+m\sqrt{n})\),可以通过。
P12195 [NOISG 2025 Prelim] Itinerary
CF827F Dirty Arkady's Kitchen
CF1637F Towers
归类:贪心、构造
一般的路径覆盖很难刻画,不过观察到此题有重要性质:【所有塔一定是建在叶子(度数为 \(1\))节点上】。否则可以将每个塔调整到叶子,覆盖到的点不减反增,一定是不劣的。然后考虑一个点可以被怎样的两个塔约束,可以发现,要么是一点在子树内,一点在子树外,要么是两点都在子树内。不过在子树外的部分很难处理,此处用到一个很巧妙的技巧,类比点分治的思想,【把根钦定在 \(h\) 最大的点处】。于是为了满足根的要求,肯定是选择了两个不同的子树中的叶子,将其拼接,那么对于其他的任何子树,都至少存在一个子树外的点满足要求:
因此只需要对根考虑拼接两个子树,对其他的子树选出一条链调整即可,简单实现。
[AGC023F] 01 on Tree
归类:贪心
注意到原来的限制就是要求按照拓扑序,对于每个点,可以看作是要在其每棵儿子子树放好之后合并上来。那么考虑这样一种思路,每次选一个放在前面最优的子树(连通块)出来,计算其贡献后,将其合并到其父亲的连通块中。如何找到放到前面最优的连通块?考虑贪心,具体而言是 \(\texttt{Exchange Argument}\) 的方法,考虑两个连通块的情况,第一个分别有 \(a_{1},b_{1}\) 个 \(0,1\),第二个有 \(a_2,b_2\) 个。那么第一个放在前面的贡献就是 \(b_1a_2\),第二个放在前面的贡献就是 \(b_2a_1\),因此第一个更优就等价于 \(b_1a_2<b_2a_1\),可以推广到多个个体的情况。因此可以维护一个堆,每次从中取出最优的连通块,将其合并到父亲。时间复杂度 \(O(n\log n)\)。
数据结构
CF1491H Yuezheng Ling and Dynamic Tree
归类:分块
\(\mathrm{lca}\) 如何刻画?想到在树上倍增跳 \(\mathrm{lca}\) 的时候,是先将两个点调到同一高度,然后再同时往上跳到同一个点,于是类比 弹飞绵羊,我们将 \(a\) 数组分块,记块长为 \(B=\sqrt{n}\),用 \(to_i\) 表示点 \(i\) 往前跳跳出所在块到达的点编号。
求 \(\mathrm{lca}\) 时,若两点将要跳到的点编号不等,即 \(to\) 不相等,那么就不断将 \(to\) 大的用 \(u\leftarrow to_u\) 跳上来,调到相等后,如果两点不一样就把编号大的用 \(u\leftarrow a_u\) 跳父亲,这样两次复杂度都是 \(O(\sqrt{n})\) 的,因此可以做到 \(O(\sqrt{n})\) 单次处理询问。
考虑区间修改怎么做,对于散块,可以暴力修改然后重构。整块看似只能暴力修改然后重构,不好处理。但是我们挖掘一下 \(to_i\) 的性质,如果 \(a_i\le i-B\) 会发生什么,此时发现 \(i\) 的父亲肯定已经跳出了 \(i\) 所在的块,那么此时 \(to_i=a_i\) 恒成立。由于开始时有 \(a_i<i\),因此可以得到每个 \(to_i\) 只会在前 \(B\) 次操作中才可能被“改变”,在超过 \(B\) 次操作后一定有 \(to_i=a_i\)。
所以每个块我们只需要暴力重构 \(B\) 次,总共 \(\sqrt{n}\) 个块,每个操作 \(\sqrt{n}\) 次,每次操作重构复杂度 \(O(\sqrt{n})\),因此整块修改 \(to_i\) 的总复杂度就是 \(O(n\sqrt{n})\) 的。而对于 \(a_i\) 的修改,可以打 \(\mathrm{tag}\) 简单实现。
于是我们就在 \(O(n\sqrt{n})\) 的时间复杂度内解决了此题。
CF1523G Try Booking
归类:分治、树套树
考虑一个优雅的暴力,定义 \(\mathrm{solve}(l,r)\) 表示时间区间 \([l,r]\) 中会分配到的人数,那么根据分配方式,会选择编号最小的请求 \([L,R]\),然后递归到两边 \(\mathrm{solve}(l,L-1)\),\(\mathrm{solve}(R+1,r)\)。对于给定的长度限制 \(x\),由于每次至少减少 \(x\) 的长度,所以这个分治算法的复杂度是 \(O\left(\frac{n}{x}\right)\) 的。如果我们对于每个 \(x\) 做一遍,就是 \(\displaystyle\sum_{i=1}^{n}\frac{n}{i}=O(n\ln n)\),因此如果我们能快速找到 【满足 \(R-L+1\ge x,[L,R]\subseteq [l,r]\) 的编号最小的 \([L,R]\)】,那么就可以 \(\mathrm{poly\log}\) 解决问题。
先按区间长度扫描线,于是需要支持插入区间、查询区间。我们采用尽量简化修改的思路,对于查询 \([l,r]\),相当于要询问 \(R\le r\) 的 \(L\ge l\) 信息;对于插入 \([L,R]\),其相当于要在 \(R\) 处更新 \(L\) 的信息。这样,我们就可以用树状数组套动态开点线段树解决,插入查询都是 \(O(\log^2 n)\),因此总时间复杂度 \(O(n\ln n\log^2 n+m\log^2 n)\),由于 \(n\le 5\times 10^4\),\(\log^3\) 的算法可以通过。
P11536 [NOISG 2023 Finals] Curtains
归类:扫描线、线段树
为了尽可能覆盖,原问题相当于判断所有 \(L\le l_i\le r_i\le R\) 能不能完全覆盖 \([L,R]\)。注意到这等价于 \(\forall x\in [L,R]\) 都有被区间覆盖到。先按照 \(R\) 扫描线,满足 \(r_i\le R\)。考虑扫描线时对于每个 \(x\) 维护 \(p_x\) 表示目前能够覆盖 \(x\) 的最大 \(l_i\)(限制最弱),那么如果满足 \(L\le p_x\) 那么 \(x\) 就可以被覆盖,因此对于所有 \(x\),条件为 \(L\le \displaystyle\min_{x\in [L,R]} p_x\)。于是只需要支持区间取 \(\max\)、区间查询 \(\min\) 即可,线段树即可解决。
QOJ.4420 Range Reachability Query
归类:bitset
、分块
这个问题不弱于 \(\mathrm{DAG}\) 可达性问题,即 \([l,r]=[1,m]\) 的情况,可以用 bitset
做到 \(O\left(\frac{n^2}{\omega}\right)\)。
那如果有编号限制,直接每次暴力跑可达性统计的复杂度连普通暴力都不如,分析到这个策略的主要问题是我们只需要查询 \(q\) 个点对的可达关系,而每次这样可达性统计就搞出 \(O(n^2)\) 个点对。
考虑并行处理询问,在每个询问的末节点处挂上这个询问,具体的,开 \(n\) 个 \(q\) 位的 bitset
,第 \(i\) 个 bitset
\(f_i\) 表示从点 \(i\) 出发,目前到每个询问末节点的可达性,即 \(f_{i}(j)=1\) 表示点 \(i\) 可以走到第 \(j\) 个询问的末节点。用 \(E_{(u,v)}\) 表示使边 \((u,v)\) 合法的询问集合,那么转移类似 \(f_{u}\leftarrow f_u\cup (f_v\cap E_{(u,v)})\),这样相当于将 \(q\) 个独立的询问放在一起,用 \(O\left(\frac{q}{\omega}\right)\) 的复杂度并行处理。考虑如何求出 \(E_{(u,v)}\),边映射到编号,本质是一个在编号上的下标,每个询问限制的编号区间是 \([l,r]\),因此异或差分一下即可快速求出 \(E\),然后按照拓扑序更新每条边 \((u,v)\),即可 \(O\left(\frac{mq}{\omega}\right)\) 求出所有 \(f\),那么对于每个询问,直接判 \(f_s(i)=1\) 即可,即从起点 \(s\) 可以走到当前询问的节点,也即两点联通。
于是似乎做完了,得到了正确的时间复杂度。但是空间 \(O\left(\frac{nq}{\omega}\right)\)似乎炸了,此处有 \(\texttt{trick}\),对询问分块,每 \(\omega=64\) 个分一段,于是每一段中的 bitset
就是一个 ull
能压进的状态,每次位运算直接变成 \(O(1)\),所以总时间复杂度还是 \(O\left(\frac{mq}{\omega}\right)\),空间优化到了 \(O(n)\)。
P11803 【MX-X9-T7】『GROI-R3』此花绽放之时
贪心
P11820 [PA 2015] 健身房 / Siłownia
归类:贪心
先放松限制,一步步来看.
如果 \(p_i\) 互不相同,那么就是一个区间选点的典中典贪心,策略就是按 \(r\) 排序从前往后扫,如果一个区间没有覆盖就选择其右端点,容易用夹逼法证明。
但是如果有 \(p_i\) 相同、不能选相同位置的限制,就可以出现以下问题:
此时右端点就只能贡献到一个黑色区间。那么选择哪个最优?注意到在我们的选择方式下,被选择的点是从前往后的,因此右端点越靠后的,越有机会会被后面的端点满足,所以此时选择右端点最靠左的是最优的。
但是如果出现右端点相同的怎么办?即如果有 \(p_i=p_j\wedge r_i=r_j\) 的区间,那么原来按照右端点最左选就无法区分区间的优劣。不妨设 \(l_i\le l_j\),此时我们发现能贡献到 \(i\) 的比能贡献到 \(j\) 的点要多,因此优先让限制小的 \(j\) 选择这个位置,而对 \(i\) 而言不能再选 \(r_i\) 了,因此 \(r_i\leftarrow r_i-1\) 即可。处理完这种情况,就是原来的贪心了。
关于贪心的实现,可以考虑对时间 \(t\) 扫描线,动态加入区间,时间复杂度 \(O(n\log n)\)。
字符串
CF587F Duff is Mad
归类:\(\texttt{ACAM}\)、均摊
考虑 \(\texttt{ACAM}\) 是如何处理一堆串在一个串中的出现次数的问题。做法是对匹配串的每一个节点跳 \(\mathrm{fail}\) 树,跳到的文本串尾节点的个数和。为了将统计放在匹配串上,可以看做将每个匹配串尾节点的子树(\(\mathrm{fail}_i\to i\))打标记,求匹配串每个节点的标记个数和。
\(l,r,k\) 的询问可以拆成前缀和形式,于是只需要处理 \(1,r,k\),即某个前缀对某个特定串的贡献。传统的 \(\texttt{ACAM}\) 算法显然不能解决这类问题了,考虑均摊,令 \(m=\displaystyle\sum_{i=1}^{n}|S_i|\),设定阈值 \(B\):
-
\(|S_k|>B\)
这样的 \(S_k\) 只有 \(O\left(\frac{m}{B}\right)\) 个,考虑对每个这样的 \(S_k\) 线性处理所有 \(r\) 的答案,将 \(S_k\) 上的所有节点打上标记,扫到 \(S_r\) 则算上其尾节点子树内的贡献,这部分可以做到 \(O\left(\frac{m^2}{B}\right)\)
-
\(|S_k|\le B\)
这意味着 \(S_k\) 跑一遍统计是 \(O(B)\),考虑对 \(r\) 做扫描线,每扫到一个 \(S_r\) 就将其尾节点的子树加上贡献,将挂在 \(r\) 上的每一个 \(S_k\) 暴力遍历每一个节点,统计其贡献和。于是需要支持子树加、单点查,子树加拍到 \(\mathrm{dfn}\) 序上就是区间加,考虑 \(O(\sqrt{m})\) 修改,\(O(1)\) 查询的分块,这样加需要 \(O(n\sqrt{m})\),查询需要 \(O(qB)\),总和起来就是 \(O(n\sqrt{m}+qB)\)
均值不等式一下,解得 \(B=\cfrac{m}{\sqrt{q}}\),总时间复杂度为 \(O(n\sqrt{m}+m\sqrt{q})\),非常厉害。
P3546 [POI 2012] PRE-Prefixuffix
分类:\(\texttt{border}\)、性质
很容易发现 \(\texttt{AB,BA}\) 形式的循环同构在原串中体现为求 \(\texttt{border}\) 然后再在剩下的部分再求一次最大 \(\texttt{border}\)。用 \(f_i\) 表示原串头尾掐掉 \(i\) 个字符后,即 \(s[i+1,n-i]\) 的最大 \(\texttt{border}\) 长度。那么答案可以表示为 \(\displaystyle\max_{i\texttt{ is border}}\set{i+f_i}\)。于是问题变成如何快速求出所有 \(f_i\)。
一个一个单次求显然很难快速处理,考虑递推,考察 \(f_i,f_{i+1}\) 之间的关系:
其中绿色是 \(f_i\) 表示的最大 \(\texttt{border}\),不难发现,去掉红色蓝色两个相等字符后,黄色部分是相等的,也即黄色部分可以作为 \(f_{i+1}\) 的 \(\texttt{border}\),其长度为 \(f_i-2\),于是我们得到重要性质:\(f_{i+1}\ge f_i-2\)。
那么问题就变得简单了,\(i\) 从大到小扫,每次 \(f_{i}\) 都有上界 \(f_{i+1}+2\),从这个上界开始暴力求出满足要求的最大 \(\texttt{border}\),可以用字符串哈希 \(O(1)\) 判断,分析一下发现总共只需要 \(O(n)\) 次判断,时间复杂度 \(O(n)\)。
构造
P8866 [NOIP2022] 喵了个喵
先考虑 \(k=2n-2=2(n-1)\) 怎么做。这启发我们使用 \((n-1)\) 个栈放卡片,每个栈最多放两个,然后再空一个栈。这样,每当一个卡牌进来,如果前 \((n-1)\) 个栈栈顶有相同颜色的,那么直接放上去就消掉了,否则如果某个栈底有颜色相同的,那么放到空栈里操作栈底也能消掉。
对于 \(k=2n-1\),比 \(k=2n-2\) 多了 \((2n-1)\) 这种颜色的牌。在此情况下,可能出现当前的牌不能立刻消掉的情况。
假设当前牌堆顶的牌 \(x\) 没有办法安置,我们转换思路,考虑找到后面的一个牌 \(y\),使得 \(y\) 在当前的栈底出现过,然后将 \(x\) 放到对应栈栈顶,这样轮到 \(y\) 放的时候,可以通过底部消除,把 \(x\) 这个栈消到符合要求:
但是这个策略并不是对于所有情况都可行,\(x\) 放到栈上面,会堵住原来的栈顶,使得原本能消除掉的牌无法消除,堵在上面:
如何解决?我们考虑把 \(x\) 放到空栈里:
但是不难发现,此时原先要消掉栈底的那个栈一定会变成空的,所以我们转变空栈,那就顺利完成了消除。
于是,我们对于所有情况都有了应对方案,可以构造:
记 \(x\) 表示当前没有办法安置的卡牌,\(y\) 表示后面在栈底出现的牌,用 \(P\) 表示 \(y\) 所在的栈,\(z\) 表示 \(P\) 的栈顶。
-
当后面的 \(z\) 放到 \(P\) 处没有剩余,即 \(z\) 的个数是偶数时
此时可以将 \(x\) 放到 \(P\) 的栈顶,后面的 \(z\) 会在栈顶消完,最后用 \(y\) 消掉 \(P\) 的栈底。
-
当后面的 \(z\) 放到 \(P\) 处有剩余,即 \(z\) 的个数是奇数时
此时可以将 \(x\) 放到空栈,此时后面的牌一定可以将 \(P\) 变空,将 \(P\) 变成新的空栈即可。
注意特殊情况,如果 \(x\) 后面有另一个 \(x\) 在 \(y\) 之前,那么可以将 \(x\) 放到空栈里,一直消到下一个 \(x\)。
P6892 [ICPC 2014 WF] Baggage
观察样例,我们猜测操作次数最少就是 \(n\) 次,且我们只会用到两个空位。
考虑边界,\(n=1,2\) 都是无解的,\(n=3\) 不止需要两个空位,\(n=4\) 在只用两个空位的情况下可行,以此为突破:
初始状态:
__BABA[BABA...BABA]BABA
目标状态:
AAAA[AAAA...BBBB]BBBB__
考虑操作:
__BA[BABA...BABA]BABA
ABBA[BABA...BABA]B__A
ABBA[__BA...BABA]BBAA
我们发现里面是一个子问题,如果可以递归解决,那么:
ABBA[AAAA...BB__]BBAA
不难发现,可以:
ABBA[AAAA...BB__]BBAA
A__A[AAAA...BBBB]BBAA
AAAA[AAAA...BBBB]BB__
所以 \(n=5,6,7\) 作为边界,每次递归到 \(n-4\),可以在 \(n\) 次操作完成。
【UNR #1】Jakarta Skyscrapers
以下令 \(a\le b\)
先考虑 \(a=1\) 怎么做,不难发现可以迭代:
那么可以凑出所有 \(2^k\),用 \((b-c)\) 二进制拆分后依次减去幂次即可。可以做到大约 \(3\log_2 b\) 次操作。我们称之为【方法】。
对于其他情况,我们有性质:能凑出 \(c\),等价于 \(\gcd(a,b) \mid c\)。充分性:因为 \(a,b\) 每次减得到的数一定都是 \(\gcd(a,b)\) 的倍数。所以我们考虑先凑出 \(\gcd(a,b)\),然后用 \(\gcd(a,b)\) 凑出 \(c\),就有了必要性。
\(\gcd\) 考虑使用辗转相除,\(\gcd(x,y)=\gcd(y,x\bmod y)\),此处假设 \(x\ge y\),那么 \(x\bmod y\) 能通过类似 【方法】倍增凑出,然后递归,操作数看似是 \(\log^2\) 级别的,但是分解复杂度是 \(\sum \log\) 的,总和起来是 \(\log\) 级别的操作数。
在得到 \(\gcd(a,b)\) 后,我们可以用 \(a\) 和 \(\gcd(a,b)\) 使用【方法】,得到 \(\gcd(a,b)\) 的幂次,凑出 \(c\)。
CF2135D1
ARC199A
非常 \(\texttt{AD-Hoc}\) 的一道题。重要突破口:\(R_i,C_i<\frac{N}{4}\)。
由于行和列都会互相影响,所以从整体考虑会无从下手。不妨先将第 \(1\) 行通过一些列操作变成全部都是 \(0\) 的,这样之后的列操作次数就恰好是 \(R_1\),是一个定值。由于行列操作顺序无关紧要,认为先操作完行再操作列。此时每一行有怎样的性质?考虑第 \(i\) 行,其目前有 \(x\) 个 \(1\),考察其在后面的 \(R_1\) 次列操作后 \(1\) 的个数,可以得到一个相当宽的上界 \(R_i\in [x-R_1,x+R_1]\),我们对 \(x\) 求出一个上界:由于 \(R_i<\frac{N}{4}\),所以 \(x-R_1<\frac{N}{4}\Rightarrow x<\frac{N}{4}+R_1\),又因为 \(R_1<\frac{N}{4}\),所以 \(\boxed{x<\frac{N}{2}}\)。也就是说,这一行想要在列操作后合法,起码需要满足 \(1\) 的个数 \(<\frac{N}{2}\)。那很明显了!
-
如果 \(x<\frac{N}{2}\),此行一定不需要反转。
-
如果 \(x>\frac{N}{2}\),此行一定需要反转。
-
如果 \(x=\frac{N}{2}\),此行无论怎么搞都是寄的,无解。
搞出了每一行是否需要反转,问题就变得简单了,对于每一列再判断一下反转即可。
数论数学
除数函数求和
归类:线性筛
明显拆贡献,答案就是
可以枚举 \(i\),重点是如何快速求出所有 \(i^k\)。直接快速幂求是 \(O(n\log k)\) 的,无法接受。
考虑 \(i^k\) 性质,我们令 \(f(x)=x^k\),由于 \((ab)^k=a^kb^k\),所以有 \(f(ab)=f(a)f(b)\),因此 \(f\) 是一个积性函数,那么我们可以在跑筛的时候顺便处理出所有的 \(f(i)\),具体的,我们有 \(f(ip)=f(i)f(p)\)
因此总时间复杂度是 \(O\left(\frac{n\log k}{\ln n}+n\right)\),可以通过。
P6620 [省选联考 2020 A 卷] 组合数问题
归类:组合数、二项式定理
首先多项式在和式下可以拆成单项式逐个求解,即我们要求
Part 1:
Part 2:
综上,我们得到了
进行化简,考虑 \(F\) 在 \(m-1\) 上的取值,即
考虑边界,用二项式定理转化:
最后我们要求的就是 \(F(N,*)\)。
\(F\) 看似是 \(N\times M\) 的,但是不难发现 \(F(N,*)\) 的取值只与 \(n\ge N-M\) 的有关,所以 \(F\) 就是 \(M^2\) 级别的,可以直接递推计算。
时间复杂度 \(O(M^2)\)
P3518 [POI 2011] SEJ-Strongbox
归类:裴属定理、\(\gcd\)
先考虑只有一个数 \(a\),此时 \((ka)\bmod n\) 都能被表示,所以 \(x\) 能被表示等价于 \(x\equiv ka\pmod n\),这是一个线性同余方程的形式,根据裴蜀定理,\(x\) 能被表示,等价于 \(\gcd(a,n) \mid x\)。
扩展这个过程,两个数 \(a,b\),则 \(x\mid \gcd(a,n)=k_1\) 和 \(y\mid \gcd(b,n)=k_2\) 都能被表示,设 \(x=d_1k_1,y=d_2k_2\),则 \(z\equiv x+y\equiv d_1k_1+d_2k_2\pmod n\),这也是一个线性同余方程的形式,同理可得此时 \(\gcd(a,b,n)\mid z\)
归纳得到,\(k\) 个数 \(a_1,a_2,\dots a_k\),能表示出数 \(x\) \(\Leftrightarrow\) \(\gcd(a_1,a_2,\dots,a_k,n) \mid x\)
对应到此题,由于 \(m_k\) 已经固定为密码之一,所以我们相当于要找到一组 \(a_1,a_2,\dots a_{tot}\),令 \(d=\gcd(n,m_k,a_1,a_2,\dots a_{tot})\),此时能表示的数就是 \(d \mid x\) 的数,由于 \(m_{1\sim k-1}\) 都不是密码,所以 \(\forall i\in[1,k-1],d\nmid m_i\)。
记 \(x=\gcd(n,m_k)\),我们可以枚举 \(g=\gcd(a_1,a_2,\dots,a_{tot})\),不难发现 \(g\mid x\),所以可以枚举 \(x\) 的因数作为 \(g\)。我们判断是否有 \(\forall i\in[1,k-1],g\nmid m_i\),如果满足,此时不同的 \(a\) 就有 \(\left\lfloor\frac{n}{g}\right\rfloor\) 个,将其取最大即可得到答案。
直接暴力判断,时间复杂度是 \(O(\sigma(x)\times k)\),无法接受。
考虑通过预处理优化判断的过程,我们要查询一个数是否是一堆数中某个的因数。首先,可以对于每个 \(m_i\) 与 \(x\) 取 \(\gcd\),显然不影响结果,因为 \(g\mid x\wedge g\mid m_i\Leftrightarrow g\mid \gcd(x,m_i)\)。然后,我们标记 \(m_i\) 的所有因数。直接枚举因数太慢,但是我们发现取 \(\gcd\) 后所有的 \(m_i\mid x\),因此可以先求出 \(x\) 的所有质因数,然后用质因数进行分解,即可快速标记所有因数。
在此方法下,本质不同的因数只有 \(O(\sigma(x))\) 个,加上记忆化打标记,总时间复杂度是 \(O(k\log x+\sqrt{x}+\sigma(x))\)
杂项
CF1774G Segment Covering
归类:区间问题、容斥、倍增
对于区间相关题目,考虑挖掘包含等性质来简化。对于此题,考虑两条包含的线段:\(A\subset B\),如果我们已经选择了 \(A\),那么 \(B\) 的选择对于整体的并集是没有影响的,并且此时选/不选 \(B\) 对于后续 \(f,g\) 的方案数贡献都是相同的,换言之,\(A\) 选择后对于 \((f-g)\) 一定没有贡献,所以相当于钦定 \(A\) 不选,因此 \(A\) 可以直接抛弃。所以我们只剩下了两两不包含的区间。注意到其性质是,排序后左端点、右端点都是严格单增的。
考虑一个询问 \([l,r]\),假设被其包含的区间为: \([l=l_1,r_1],\dots [l_k,r_k=r],l_1< \dots <l_k,r_1< \dots < r_k\),首先 \([l_1,r_1]\) 是必选的,然后从前往后考虑每个区间,假设有区间 \([l_1,r_1],[l_2,r_2],[l_3,r_3]\):
---1---
----2----
-----3-----
如上,为了保证连续,这三个区间都必须入选。
---1---
----2----
-----3-----
如上,如果我们选择了 \([l_3,r_3]\),那么中间的 \([l_2,r_2]\subset [l_1,r_1]\cup [l_3,r_3]\),所以类比包含的思考过程,选 \([l_3,r_3]\) 对 \((f-g)\) 是没有贡献的。并且由于要连续,\([l_2,r_2]\) 就是必选的,注意到最后这样选得的区间就是:
---1--- ---3--- --5--
---2--- ----4----
相当于钦定这些区间必选,记区间个数为 \(k\),那答案就是 \((-1)^k\)。
具体实现时,先保留不包含其他区间的区间,对于询问,判断能不能构成并集恰好为 \([l,r]\) 的区间集,不能答案就是 \(0\)。对于有方案的情况,构造这些区间以求出区间个数,相当于上下选择两个初始的区间,每次都往后跳 \(l_j>r_i\) 的区间,直到覆盖完,可以倍增跳,跳完之后,若选到同一点或者最后没有跳到 \(r\),说明舍弃掉无用区间后没有方案,那么答案就是 \(0\),否则记录跳过的区间个数,答案就是 \((-1)^k\),可以 \(O(n\log n)\) 解决。
P12018 [NOISG 2025 Finals] 机器人
CF1896D Ones and Twos
类人思维题。Instead of 思考有哪些和可以被得到,我们逆向思考,假设现在有一个和为 \(x\) 的区间 \([l,r]\),可以得到什么?\(a_i\) 只有 \(1,2\) 是很重要的,分讨一下 \((a_l,a_r)\) 的取值,我们发现:
- \((1,1)\),将区间缩到 \([l+1,r-1]\),和变成 \(s-2\)。
- \((1,2)\),将区间缩成 \([l,r-1]\),和变成 \(s-2\)。
- \((2,1)\),将区间缩成 \([l+1,r]\),和变成 \(s-2\)。
- \((2,2)\),将区间缩成 \([l,r-1]\) 或 \([l+1,r]\),和变成 \(s-2\)。
这意味着,任意一个和为 \(s\) 的区间都可以得到和为 \(s-2\) 的区间。因此问题变成找到和 \(x\) 同奇偶的最大区间和 \(s\),询问是否有 \(s\ge x\),那么首先 \([1,n]\) 作为 \(\left(\sum a\right)\bmod 2\) 的选择,而与整体和异奇偶的可以找到左右两端最靠外的 \(1\),位置记为 \(p,q\),则 \([p+1,n],[1,q-1]\) 可以作为选择,取 \(\max\) 作为 \(s\) 即可。
于是可以用 set
维护 \(1\) 的位置,用树状数组维护区间和即可,时间复杂度 \(O( n \log n)\)。