FJOI2020 的两道组合计数题

最近细品了 FJOI2020 的两道计数题,感觉抛开数据范围不清还卡常不谈里面的组合计数技巧还是挺不错的。由于这两道题都基于卡特兰数的拓展,所以我们把它们一并研究掉。

首先是 D1T3 ,先给出简要题意:

  • \(4\) 个栈 \(s_{1 \ldots 4}\),初始时 \(s_1\) 从栈底到栈顶为 \(1,2, \ldots, n\)\(s_{2 \ldots 4}\) 为空。
  • 接下来,对它们进行若干次操作,每次操作可以任选 \(i \in \{1,2,3\}\),并将 \(s_i\) 的栈顶弹出并压入到 \(s_{i+1}\) 中。
  • 要求任意时刻 \(s_2\) 从栈底到栈顶单调递减,\(s_3\) 从栈底到栈顶单调递增,同时全部操作结束后所有元素都被移入 \(s_4\) 中。
  • 求出全部操作结束后不可能出现在 \(s_4\) 中的排列个数,对 \(m\) 取模。
  • 多组询问, \(n\le 5\times 10^5\)

是否感觉到十分困难啊(如果秒了的话请自行节约时间)?那么我们还是从简单情况开始考虑。如果 \(n=1,2,3\) 的话那答案当然是 \(0\)\(n=4\) 呢?借助计算机打表,可以发现只有两种情况不合法,分别是 \(P_1=1,4,2,3\)\(P_2=2,4,1,3\) 。再强行打出 \(n=5\) 的表,我们可以惊奇地发现,每一个不合法的排列都存在一个长度为 \(4\) 的子序列满足将其离散化之后为 \(P_1\)\(P_2\)

虽然之后我们很快就打不动表了,但我们可以大胆猜想:排列不合法是否等价于其存在一个离散化后为 \(P_1\)\(P_2\) 的长为 \(4\) 的子序列?

为此,我们需要探究 “ \(P_1\)\(P_2\) ”这一约束更直观的意义。可以发现它就相当于约束 “小,大,小,中” 不存在于排列之中。接着我们可以尝试使用归纳法:

对于一个满足约束的结果排列 \(Q\) ,找到其中 \(n\) 对应的位置。如果 \(n\) 在开头那么我们先把它移到栈 \(4\) 之后就变成了 \(n-1\) 的情况。

否则的话,我们利用约束条件,找到 \(n\) 之前的最小值 \(k\) ,把序列划分为 \(3\) 个部分: \(n\) 之前的一个部分, \(n\)\(n\) 之后大于 \(k\) 的数一个部分, \(n\) 之后小于 \(k\) 的数一个部分。根据约束条件,第 \(2\) 部分的元素必然都在第 \(3\) 部分的元素之前(否则子序列 \(k,n,3x,2y\) 不合法),且 \(2\) 部分的元素单调递降(否则找到一个上升位 \(2x,2y\) ,子序列 \(k,n,2x,2y\) 不合法)。

\(1,3\) 部的元素个数都小于 \(n\) ,根据归纳法我们可以对其寻得一组操作序列。接着我们考虑拼出 \(n\) 的操作序列。对于 \(1\) 的操作序列,使其所有弹 \(2,3\) 栈的操作不变,在弹 \(1\) 栈时顺便把该元素上方的未弹元素弹出,进行完此段操作之后我们就得到了第 \(1\) 部分且 \(2\) 栈中的序列与第 \(2\) 部分相同, \(3\) 栈为空。我们先把 \(2\) 栈中的所有的元素弹到 \(3\) 栈再把 \(3\) 栈中的所有元素弹到 \(4\) 栈,就得到了第 \(2\) 部分,最后再把第 \(3\) 部分的操作序列与之拼接即可。于是我们得到了 \(n\) 的操作序列。

而必要性是显然的,如果我们把存在此子序列的排列移动成功了,那么单纯考虑对子序列的移动操作,把子序列换成离散化之后的值,就马上得到了 \(Q_1\)\(Q_2\) 的合法移动方法,这显然是不可能的。

那么我们就能证明排列合法等价于其不存在“小,大,小,中”子序列。接下来考虑计数。可以尝试先容斥成所有合法排列的个数,再按值从小到大进行增量。对于加入 \(i\) 前的序列,可以发现加入 \(i\) 等价于使 \(i\) 前面的任意两个元素之间都无法插入元素且其余位置仍都可以插入元素,那么这就相当于把 \(i\) 前面的所有元素合并成一个元素。那么我们可以直观地设计出一个 DP : \(f_{i,j}\) 表示加入前 \(i\) 个数后有 \(j\) 个元素的方案数。那么边界条件 \(f_{1,1}=1\) ,枚举加入前有几个元素可得 \(f_{i,j}=2f_{i-1,j-1}+\sum_{k\ge j}f_{i-1,k}=2f_{i-1,j-1}+f_{i,j+1}-f_{i-1,j}\) ,那么我们就可以进行 \(O(n^2)\) 的 DP ,最后答案就是 \(\sum_j f_{n,j}\)

怎么优化呢?我们自然还是考虑先打表找规律,可以发现 \(n\) 的答案就是 \(f_{n+1,2}\) (注意边界 \(1\) 要特判)。其实这是因为对于一个 \(n\) 的方案案在其末尾加入 \(n+1\) 后就能构造与 \(f_{n+1,2}\) 的一一对应。

接下来就是喜闻乐见的二维平面建模了。我们把转移式放到二维平面上,把状态当成点,转移当成有向边,观察一下答案的组合意义。可以发现它就是所有 \((2,2)\)\((n+1,2)\) 的路径边权乘积的和的两倍。因此我们换个枚举方式,枚举路径上有多少条从左上到右下的转移边,就能得到

\[Ans=2\sum_{i=0}^{n-1} 2^i(-1)^{n-1-i}\binom{n-1+i}{2i}C_i \]

其中前面部分是权值的计算,后面部分是简单的计数。首先用 \(C_i\) 即卡特兰数算出斜右下转移边与向左转移边的拼接方案数,再用组合数算出向下转移边与其它转移边的拼接方案数,相乘即得到所有方案数。

至此我们假装 \(m\) 是大质数,就有了个 \(O(n)\) 的算法。交到 cfcs 的算法在线平台上你会收获一个 TLE ,这是因为这个式子常数属实巨大。把组合数与卡特兰数展开对消简化一下式子再交上去,多试几次就差不多能过了。是不是除了最后的卡常部分和模数未知部分都很不错呢?

然后是 D2T2 ,这题虽然也神仙,但在 D1T3 面前还是略显拉跨的。简要题意:求把一个凸多边形划分为 \(n(n\le 10^6)\) 个边数为 \(k(3\le k\le 200)\) 的凸多边形的方案数。多组询问,只需输出所有询问答案之和,对 \(10^9+7\) 取模。

可以发现 \(m=3\) 就是简单的卡特兰数,注意把大组合数转为下降幂除以阶乘计算即可。

那么我们如何推广呢?可以考虑利用卡特兰数中常见的折线表示。首先思考怎么表示一种方案。

我们从一个点开始按逆时针顺序加边走到下一个点(相当于向后的高度变化量为 \(+1\) 的折线),一条连接当前点与之前点的边就会划分出一个多边形(相当于删去 \(m-1\) 条边并加入一条边,也即一条向后的高度变化量为 \(-m+2\) 的折线)。

按这样的顺序做完就把一个多边形划分唯一对应到了一条折线,由于加入第一条边之后任何时刻都至少有一条边没被删去,所以折线除第一个点外高度都大于 \(0\) 。同时满足这个条件的折线都能对应到一个多边形划分方案,那么我们就建立了多边形划分与合法折线的一一对应,所以划分的个数就是合法折线的条数。

那么合法折线的个数如何计算呢?我们不妨考虑所有 \(\binom{nm-n+1}{n}\) 条有 \(n\) 个下降线的折线。如果将一条折线从最后一个最低点断开做循环移位,那么它刚好对应到一条合法折线。同时,一条合法折线所有 \(nm-n+1\) 个循环移位恰对应 \(nm-n+1\) 条不同的折线。又由于对应关系可逆,所以合法折线的 \(nm-n+1\) 倍就是 \(\binom{nm-n+1}{n}\) ,那么其个数也就十分明朗了。

posted @ 2021-04-02 17:46  林政宇  阅读(340)  评论(0编辑  收藏  举报