[组合数学] CF1750G Doping

CF1750G Doping

很好的题,也从 Alex_Wei 的题解里学到了很多,在写代码之前先写一份记录,算是把思路过一遍。

题意简述

对于给定排列 \(p\),定义 \(f(p)\) 表示排列中连续段的数量。其中,连续段的定义为对于一段连续下标 \([l,r]\),有 $ \forall\ i\in [l,r-1], \ x_{i+1} = x_i + 1$。给出排列 \(a\),对于每个 \(k\),求出字典序小于 \(a\) 的排列 \(p\) 中, \(f(p)=k\) 的排列数。 数据范围:\(n \le 2000\)

分析

先考虑一个更加简单的问题:没有字典序的限制,求 \(f(p)=k\) 的排列数量。对于这个问题,经典的做法是二项式反演。但排列有两个维度:位置和值,应该对于哪一个维度做二项式反演呢?这个时候结合原问题来看一看:原问题要求字典序小于给定的 \(a\),常见的做法是枚举 LCP 长度,然后对于 LCP 的后一位,要求 \(p_L < a_L\)。如果这样来考虑,那么排列的值可能被拆成若干个段,但是位置依然是连续的( \(L+1\)\(n\) 的所有位置都还没有填,可以用来反演),所以考虑钦定值连续,即对值做反演(反过来想,如果对位置做反演,即钦定连续的一段位置是一个连续段的话,可能出现没有足够长的值连续段能够满足这么长的位置的情况)。

\(G_k\) 表示钦定 \(k\) 个位置满足 \(i+1\)\(i\) 相邻 的方案数,\(F_K\) 表示恰好 \(k\) 个位置满足 \(i+1\)\(i\) 相邻的方案数。考虑计算 \(G_k\):可以想一想排列生成的过程。一开始有 \(n\) 个独立的元素,每次钦定一个 \(i+1\)\(i\) 相邻,相当于将两个段合并,所以最后剩下 \((n-k)\) 个段。这 \((n-k)\) 个段的顺序是任意的,因而有 \((n-k)!\)。同时,要从 \(n-1\)\(i,i+1\) 中选出 \(k\) 对,还要乘上 \(\binom{n-1}{k}\)。所以,有:

\[G_k=\dbinom{n-1}{k} (n-k)! \]

在求出 \(G_k\) 之后,就可以二项式反演求出 \(F_k\) 了。在 Alex_Wei 的题解里对于二项式反演做了很好的讲解,这一点会写在笔记里面,此处篇幅原因,就先继续往下了。

根据上面的内容,我们可以得到一个 \(O(n^4)\) 的做法(枚举 LCP,枚举 \(p_L\) 的值,然后 \(O(n^2)\) 的二项式反演),显然无法通过。首先注意到枚举 \(p_L\) 的具体值是没有必要的。因为影响到后面二项式反演的是连续段的个数,而对于不同的 \(p_L\),连续段个数的不同情况只有 \(O(1)\) 种,因而复杂度来到了 \(O(n^3)\)

接下来进入到本题最关键的部分:枚举 LCP 必不可少,那么就意味着无法每次 \(O(n^2)\) 进行二项式反演。理想的情况是我们可以算出来每个 LCP 情况下的 \(G\),然后统一做一遍二项式反演求出 \(F\)。这看起来似乎可行,但实际上具有很大问题:

当枚举到一个 LCP 时,它本身可能有 \(c\)\(i\)\(i+1\) 相邻的情况,然后后面又新增了 \(k\) 个,那么它的方案数应该贡献到 \(c+k\) 一项。但是二项式反演是对于 \(L+1\)\(n\) 的部分进行的,对应的 \(G\)\(F\) 的下标是 \(k\) 而不是 \(c+k\),因而没有办法直接加过去。

于是本题优化到 \(O(n^2)\) 最大的难点出现了:下标偏移。这既是难点,也是解决问题的突破口。我们先考虑如果下标向右偏移1位,该怎么进行计算(因为 \(c \ge 0\),所以偏移一定是向右的):

\(F'_k=F_{k-1}\),同时有:

\[G_k= \sum_{k'=k}^{n-1} \dbinom{k'}{k} \ F_{k'} \]

\[G'_k= \sum_{k'=k}^{n-1} \dbinom{k'}{k} \ F'_{k'} \]

尝试将 \(G'_k\)\(G_k\) 建立联系:将 \(F'_k=F_{k-1}\) 代入,有:

\[\begin{aligned} G'_k=\sum_{k'=k}^{n-1} \dbinom{k'}{k} \ F_{k'-1} \\ \end{aligned} \]

\[\begin{aligned} =\sum_{k'=k}^{n} \dbinom{k'-1}{k} \ F_{k'-1} \ + \sum_{k'=k}^{n} \dbinom{k'-1}{k-1} \ F_{k'-1} \\ =\sum_{k'=k}^{n-1} \dbinom{k'-1}{k} \ F_{k'} \ + \sum_{k'=k-1}^{n} \dbinom{k'}{k-1} \ F_{k'} \end{aligned} \]

\[=G_k + G_{k-1} \]

从生成函数的观点来看,有 \(G'=G \times (1+x)\),从而如果右移 \(c\) 位的话,就相当于乘上 \((1+x)^c\),从而可以推出:

\[G'_k=\sum_{i=0}^{c} \ \dbinom{c}{i} \ G_{k-i} \]

于是,设 \(H_k\) 表示对于 \(G'_k\) 求和的结果。那怎么计算 \(H\) 呢?如果直接算,依然是 \(O(n^3)\) 的。考虑 \(H_k\)\(H_{k-1}\),先把 \(H_k\) 算1次,然后 \(H_k\)\(H_{k-1}\) 就都还需要 \({k-1}\) 次,可以合并在一起算(据说这就是秦九韶算法?),然后复杂度就 \(O(n^2)\) 了。

最后要考虑一些实现细节。在计算的时候,枚举 \(p\)\(1\)\(L-1\) 位固定,然后对第 \(L\)\(n\) 位做二项式反演,但第 \(L\) 位和第 \(L-1\) 位显然是有关联的,因而这里有一些细节需要考虑。设 \(1\)\(L-1\) 位的值组成的集合为 \(S\),当第 \(L\) 位的值是 \(p_l\) 的时候,对于后面可选的连接数,有以下两种情况:

  • \(p_{L}-1 \in S\),那么对于后面可选的连接数没有影响。

  • \(p_{L}-1 \notin S\),那么此时 \((p_{L}-1,p_L)\) 这一对连接就没有办法选了,所以后面可选的连接数要减 \(1\)

还有一个细节,如果此时选的 \(p_L\) 恰好等于 \(p_{L-1}+1\),那么此时前面的连接数从 \(c\) 变成了 \(c+1\),因而此时还有一些方案数要贡献到 \(G'_{k+1}\) 的一项。

于是,终于,做完了!!!(这篇理解得倒是比较快,但细节想和写了好一会儿,这篇记录更是写了非常之久!)

代码

总结

这道题(以及 Alex_Wei 非常 great 的题解),首先让我第一次真正 get 到二项式反演。第二点是本题中体现的下标偏移的处理以及与生成函数的关联,都是很有意义的内容。

posted @ 2026-01-30 16:50  lyc2049  阅读(1)  评论(0)    收藏  举报