最近细品了 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}\) ,那么其个数也就十分明朗了。

FJOI2020 的两道组合计数题的更多相关文章

  1. 两道相似KMP题

    1.POJ 3450 Coporate Identity 这两题的解法都是枚举子串,然后匹配,像这种题目以后可以不用KMP来做,直接字符串自带的strstr函数搞定,如果字符串未出现,该函数返回NUL ...

  2. 又一道简单题&&Ladygod(两道思维水题)

    Ladygod Time Limit: 3000/1000MS (Java/Others)     Memory Limit: 65535/65535KB (Java/Others) Submit S ...

  3. 一道cf水题再加两道紫薯题的感悟

    . 遇到一个很大的数除以另一个数时,可以尝试把这个很大的数进行,素数因子分解. . 遇到多个数的乘积与另一个数的除法时,求是否能整除,可以先求每一个数与分母的最大公约数,最后若分母数字为1,则证明可整 ...

  4. 算法(JAVA)----两道小小课后题

    LZ最近翻了翻JAVA版的数据结构与算法,无聊之下将书中的课后题一一给做了一遍,在此给出书中课后题的答案(非标准答案,是LZ的答案,猿友们可以贡献出自己更快的算法). 1.编写一个程序解决选择问题.令 ...

  5. JAVA算法两道

    算法(JAVA)----两道小小课后题   LZ最近翻了翻JAVA版的数据结构与算法,无聊之下将书中的课后题一一给做了一遍,在此给出书中课后题的答案(非标准答案,是LZ的答案,猿友们可以贡献出自己更快 ...

  6. 『ACM C++』Virtual Judge | 两道基础题 - The Architect Omar && Malek and Summer Semester

    这几天一直在宿舍跑PY模型,学校的ACM寒假集训我也没去成,来学校的时候已经18号了,突然加进去也就上一天然后排位赛了,没学什么就去打怕是要被虐成渣,今天开学前一天,看到最后有一场大的排位赛,就上去试 ...

  7. 两道人数多,课程少,query多的题

    #每天进步一点点# 来两道很相似的题目~ (智商啊智商.....) hihoCoder #1236:Scores (简单的分桶法+bitset) 2015 Beijing Online的最后一题.题目 ...

  8. 【T-SQL基础】01.单表查询-几道sql查询题

    概述: 本系列[T-SQL基础]主要是针对T-SQL基础的总结. [T-SQL基础]01.单表查询-几道sql查询题 [T-SQL基础]02.联接查询 [T-SQL基础]03.子查询 [T-SQL基础 ...

  9. Linux运维跳槽必备的40道面试精华题(转)

    Linux运维跳槽必备的40道面试精华题(转)   下面是一名资深Linux运维求职数十家公司总结的Linux运维面试精华,助力大家年后跳槽找个高薪好工作. 1.什么是运维?什么是游戏运维? 1)运维 ...

随机推荐

  1. Java(13)详解构造方法

    作者:季沐测试笔记 原文地址:https://www.cnblogs.com/testero/p/15201600.html 博客主页:https://www.cnblogs.com/testero ...

  2. 利用 CSS Overview 面板重构优化你的网站

    本文将向大家介绍 Chrome 87 开始支持的 CSS Overview Panel,并且介绍如何更好地利用这个面板.通过 CSS Overview Panel,可能可以帮助我们: 更准确(高保真) ...

  3. 【UE4 C++ 基础知识】<11>资源的同步加载与异步加载

    同步加载 同步加载会造成进程阻塞. FObjectFinder / FClassFinder 在构造函数加载 ConstructorHelpers::FObjectFinder Constructor ...

  4. 【c++ Prime 学习笔记】第8章 IO库

    C++语言不直接处理输入输出,而是通过标准库中的一组类来处理IO 1.2节介绍的IO库: istream(输入流)类型,提供输入 ostream(输出流)类型,提供输出 cin,是istream对象, ...

  5. 【数据结构与算法Python版学习笔记】树——树的遍历 Tree Traversals

    遍历方式 前序遍历 在前序遍历中,先访问根节点,然后递归地前序遍历左子树,最后递归地前序遍历右子树. 中序遍历 在中序遍历中,先递归地中序遍历左子树,然后访问根节点,最后递归地中序遍历右子树. 后序遍 ...

  6. 如何接入 K8s 持久化存储?K8s CSI 实现机制浅析

    作者 王成,腾讯云研发工程师,Kubernetes contributor,从事数据库产品容器化.资源管控等工作,关注 Kubernetes.Go.云原生领域. 概述 进入 K8s 的世界,会发现有很 ...

  7. csp-s 2021

    T1 廊桥分配 当一架飞机抵达机场时,可以停靠在航站楼旁的廊桥,也可以停靠在位于机场边缘的远机位. 乘客一般更期待停靠在廊桥,因为这样省去了坐摆渡车前往航站楼的周折. 然而,因为廊桥的数量有限,所以这 ...

  8. 「刷题」THUPC泛做

    刷了一下,写一下. T1. 天天爱射击 可以这样想. 我们二分一下每一块木板在什么时刻被击碎. 然后直接用主席树维护的话是\(O(nlog^2n)\)的. 会\(T\),而且是一分不给那种... 那么 ...

  9. matplotlib.legend()函数用法

    用的较多,作为记录 legend语法参数如下: matplotlib.pyplot.legend(*args, **kwargs) 几个暂时主要用的参数: (1)设置图例位置 使用loc参数 plt. ...

  10. Spring Security:简单的保护一个SpringBoot应用程序(总结)

    Spring Security 在 Java类中的配置 在 Spring Security 中使用 Java配置,可以轻松配置 Spring Security 而无需使用 XML . 在Spring ...