0. 前言本文章介绍内容难度不超过提高组难度,属于笔者提高组复习计划的一部分。
1. 卡特兰数 1.1 介绍卡特兰数作为广泛出现在OI中的一类特殊数列,第i i i 项改为C i C_{i} C i ,其拥有广泛的意义。数列的前几项为:1, 1, 2, 5, 14, 42, 132, 429, 1430, 4862。
这些特殊的数字,在信息学竞赛里许多题目都有这个数列的存在,可以在打表找规律时激发灵感。
我们通过一个经典例子来说明,即折线问题,如下图:
在平面上从( 0 , 0 ) (0,0) ( 0 , 0 ) 走到( n , n ) (n,n) ( n , n ) ,每次只能向上或者向右走,不穿过 y = x y=x y = x 这条直线有多少种方案。
1.2 朴素解法我们考虑枚举最后一个y = x y=x y = x 上的点( i , i ) (i,i) ( i , i ) ,可以把问题规模缩小。
我们强制钦定最后一段不碰到y = x y=x y = x ,我们在( i , i ) (i,i) ( i , i ) 继续向终点走去的过程中,第一步只能向右走,到( n , n ) (n,n) ( n , n ) 的最后一步只能向上走,中间的问题我们可以视作从( i , i + 1 ) → ( n , n − 1 ) (i,i+1)\to (n,n-1) ( i , i + 1 ) → ( n , n − 1 ) 不穿过y = ( x − 1 ) y=(x-1) y = ( x − 1 ) 的直线方案数,平移有:
中间这段答案就是C n − i − 1 C_{n-i-1} C n − i − 1 ,故可以求出C n = ∑ i = 0 n − 1 C i C n − i − 1 C_{n}=\sum\limits_{i=0}^{n-1} C_{i}C_{n-i-1} C n = i = 0 ∑ n − 1 C i C n − i − 1 。 在众多卡特兰数所对应的题目如出栈顺序,括号匹配中,缩小问题规模是一个很经典的做法。
但是这样的时间复杂度为O ( n 2 ) O(n^2) O ( n 2 ) ,我们考虑能否得到一个O ( n ) O(n) O ( n ) 或者O ( 1 ) O(1) O ( 1 ) 的过程。
1.3 容斥解法本解法请仔细理解,因为会涉及到第二章的讲解。
正难则反,我们考虑用所有路径数减去不合法路径的数量。总的是( 2 n n ) \dbinom{2n}{n} ( n 2 n ) ,我们思考不合法路径的数量,考虑把一条不合法路径进行操作: 把一条不合法路径在第一次触碰到y = x + 1 y=x+1 y = x + 1 的这条线的点设为p p p ,我们在 p p p 右侧的在y = x + 1 y=x+1 y = x + 1 以下的部分全部沿 对称到上方,我们发现这样翻转之后,所有可能出现的不合法路径,和( 0 , 0 ) → ( n − 1 , n + 1 ) (0,0)\to (n-1,n+1) ( 0 , 0 ) → ( n − 1 , n + 1 ) 的路径产生了一一映射的关系。
所以我们就有C n = ( 2 n n ) − ( 2 n n − 1 ) = 1 n + 1 ( 2 n n ) C_{n}=\dbinom{2n}{n}-\dbinom{2n}{n-1}=\dfrac{1}{n+1}\dbinom{2n}{n} C n = ( n 2 n ) − ( n − 1 2 n ) = n + 1 1 ( n 2 n ) 。
1.4 总结与经典应用C n C_{n} C n 共两个公式,一个C n = ∑ i = 0 n − 1 C i C n − i − 1 C_{n}=\sum\limits_{i=0}^{n-1} C_{i}C_{n-i-1} C n = i = 0 ∑ n − 1 C i C n − i − 1 ,一个C n = ( 2 n n ) − ( 2 n n − 1 ) = 1 n + 1 ( 2 n n ) C_{n}=\dbinom{2n}{n}-\dbinom{2n}{n-1}=\dfrac{1}{n+1}\dbinom{2n}{n} C n = ( n 2 n ) − ( n − 1 2 n ) = n + 1 1 ( n 2 n ) 。
一些等价模型:
由n n n 对左右括号构成的合法的括号序列数。 栈(无穷大)的进栈序列{ 1 , 2 , … , n − 1 , n } \{1,2,\dots,n-1,n\} { 1 , 2 , … , n − 1 , n } 由多少个不同的出栈顺序。 n n n 个1 1 1 和n n n 个− 1 -1 − 1 构成2 n 2n 2 n 项a 1 , a 2 , … , a n a_1,a_2,\dots,a_{n} a 1 , a 2 , … , a n 的任意前缀和≥ 0 \ge 0 ≥ 0 的序列数量个数。n n n 个节点可以构造出多少个不同的二叉树。圆上选择2 n 2n 2 n 个点,将这些点成对连接起来使得所得到的n n n 条线段不相交的方法数? 有经验的不难发现前四个性质是完全一致的,而最后一个可以进一步推论。
而卡特兰数计数的本质,是任何前缀中总存在某种约束的不可逆平衡(如左括号数≥ \ge ≥ 右括号数)。
1.5 例题例题是多的,这里举例几个经典应用。
我们思考要求强制使用n n n 个举行有没有什么特殊性质,我们发现一个矩形,其左下角这个矩形,一定右上角是某个拐点,否则我们会发现,我们需要额外使用一个矩形去覆盖这个拐点。
那么我们可以缩小问题规模,每次对这个阶梯状物,进行一个枚举覆盖左下角这个矩形的右上角是哪一个拐点,不妨设为第x x x 个拐点,那么上面就是一个x − 1 x-1 x − 1 的子问题,下面为n − x − 1 n-x-1 n − x − 1 的子问题,不难发现答案就是C n = ∑ i = 0 n − 1 C i C n − i − 1 C_{n}=\sum\limits_{i=0}^{n-1} C_{i}C_{n-i-1} C n = i = 0 ∑ n − 1 C i C n − i − 1 ,即卡特兰数。
期望是困难的,我们直接考虑统计n n n 个点可能的二叉树数量f n f_{n} f n 和其叶子总数g n g_{n} g n 。注意到f n f_n f n 就是卡特兰数,而g n g_{n} g n 如何计算呢?打表发现g n = n f n − 1 g_{n}=nf_{n-1} g n = n f n − 1 ,证明太长了不想过多证明(逃)。
答案就是g n f n = n ( n + 1 ) 4 n − 2 \dfrac{g_n}{f_n}=\dfrac{n(n+1)}{4n-2} f n g n = 4 n − 2 n ( n + 1 ) 。
众所周知的是,冒泡排序的交换次数就是排列的逆序对数。
首先不考虑字典序(即特殊性质),我们如何计算有多少个排列满足逆序对数等于1 2 ∑ i = 1 n ∣ i − p i ∣ \dfrac{1}{2} \sum\limits_{i=1}^n |i-p_{i}| 2 1 i = 1 ∑ n ∣ i − p i ∣ 。我们考虑原式子为c n t = 1 2 ∑ i = 1 n ∣ i − p i ∣ cnt=\dfrac{1}{2} \sum\limits_{i=1}^n |i-p_{i}| c n t = 2 1 i = 1 ∑ n ∣ i − p i ∣ ,变形有2 ⋅ c n t = ∑ i = 1 n ∣ i − p i ∣ 2\cdot cnt=\sum\limits_{i=1}^n |i-p_{i}| 2 ⋅ c n t = i = 1 ∑ n ∣ i − p i ∣ ,其中后面的式子我们考虑用图论刻画,即i → p i i\to p_{i} i → p i 进行连边,那么∣ i − p i ∣ |i-p_{i}| ∣ i − p i ∣ 就表示跨过的距离,那么合法当且仅当每条边穿过的格子正好对应它参与的逆序对的个数 ,即所有逆序对两两配对成边的端点,且没有多余交叉。
也就是说,逆序对必须两两配对,不可能出现一个位置被两个人抢走配对的情况。
等价的转化提议,即不存在三元即以上的序列满足i < j < k i<j<k i < j < k 使p i > p j > p k p_{i}>p_{j}>p_{k} p i > p j > p k 。即不存在三元即以上的下降子序列。
那么这样如何刻画呢?我们发现这玩意由于没有一元子序列这一说法,那么必定为二元下降子序列,我们用图来表示合法序列数变化这一过程:
我们发现拐点必然是当前时刻序列的最大值然后接一个较小值,然后再继续上升。
故有一个 DP,设f ( i , j ) f(i,j) f ( i , j ) 表示前i i i 个数构成的排列最大值为j j j 的方案数,转移为f ( i , j ) = f ( i − 1 , j ) + f ( i , j − 1 ) f(i,j)=f(i-1,j)+f(i,j-1) f ( i , j ) = f ( i − 1 , j ) + f ( i , j − 1 ) ,要求j ≤ i j\le i j ≤ i 。这玩意是搞笑的O ( n 2 ) O(n^2) O ( n 2 ) ,但是不难发现这玩意就是格路计数但是有j ≤ i j\le i j ≤ i 的限制,可以转化为卡特兰数,可以O ( 1 ) O(1) O ( 1 ) 计算,或者公式为( i + j i ) − ( i + j j + 1 ) \dbinom{i+j}{i}-\dbinom{i+j}{j+1} ( i i + j ) − ( j + 1 i + j ) 。
现在考虑有字典序的限制,这个限制我们可以转化为至少一个位置满足p i > q i p_{i}> q_{i} p i > q i ,其余任意。我们考虑枚举法确定这个位置什么,我们钦定一个位置i i i ,前面的和q i q_{i} q i 一致,第i i i 个必须大于p i p_{i} p i 。令m x = max j = 1 i − 1 q j mx=\max_{j=1}^{i-1} q_{j} m x = max j = 1 i − 1 q j ,m n mn m n 为最小可以填的数。
由于我们强制钦定之后从( 1 , 1 ) → ( n , n ) (1,1) \to (n,n) ( 1 , 1 ) → ( n , n ) 的f f f 的计算就不再适用了,我们将其定义为从( i , j ) (i,j) ( i , j ) 走到( n , n ) (n,n) ( n , n ) 的方案数。
接下来我们考虑如何计算方案数,考虑分类讨论:
若p i = m n p_{i}=mn p i = m n ,那么我们显然只能填写x > m x x>mx x > m x 的方案,即f ( i , m x + 1 ) f(i,mx+1) f ( i , m x + 1 ) 。 若m n < p i < m x mn<p_{i}<mx m n < p i < m x ,显然只能填写x > m x x>mx x > m x ,但是显然这样构成不合法排列了,故无解。 若p i ≥ m x p_{i}\ge mx p i ≥ m x ,只需要填写一个x > p i x>p_{i} x > p i 的数就可以了,即f ( i , p i + 1 ) f(i,p_{i}+1) f ( i , p i + 1 ) 。 时间复杂度O ( n ) O(n) O ( n ) 。
2. 反射容斥 2.1 反射法介绍与卡特兰三角有些人会将反射法叫做映射法,这里本文章称作反射法。
回看 1.3 的解法,我们通过构造y = x + 1 y=x+1 y = x + 1 的直线,然后将终点进行对称构造双射。我们将这种通过构造直线然后对称构造的类似方案,把每一条第一次越界的坏对象反射为一个更容易计数的对象,从而用总数减去反射得到的坏数得到合法对象数。我们称作反射法。
我们来介绍一个经典应用,当然不是卡特兰数因为介绍过了,我们介绍卡特兰三角。
定义C ( n , k ) C(n,k) C ( n , k ) 表示由n n n 个1 1 1 ,k k k 个− 1 -1 − 1 构成的序列中,前缀和不小于零的序列数。说人话就是还有y = x y=x y = x 的限制,但是是从( 0 , 0 ) (0,0) ( 0 , 0 ) 走到( n , k ) (n,k) ( n , k ) 。
通过上面 1.3 的方法我们当然可以构造一个直线y = x − 1 y=x-1 y = x − 1 来构造双射:
那么原命题类似的,可以转化为从( 0 , 0 ) → ( k − 1 , n + 1 ) (0,0)\to (k-1,n+1) ( 0 , 0 ) → ( k − 1 , n + 1 ) 的自由路双射,原命题即为( n + k k ) − ( n + k k − 1 ) \dbinom{n+k}{k}-\dbinom{n+k}{k-1} ( k n + k ) − ( k − 1 n + k ) 。
同时不难有性质:C ( n , k ) = ∑ i = 0 k C ( n − 1 , i ) = ∑ i = k n C ( i , k − 1 ) C(n,k)=\sum\limits_{i=0}^k C(n-1,i)=\sum\limits_{i=k}^n C(i,k-1) C ( n , k ) = i = 0 ∑ k C ( n − 1 , i ) = i = k ∑ n C ( i , k − 1 ) 。
例题:P1641 [SCOI2010] 生成字符串
2.2 双线反射容斥而多数时候,限制条件不仅仅想卡特兰数中y = x y=x y = x 一条直线的约数,可能是多重约数。例如纵坐标必须限定在[ 0 , m ] [0,m] [ 0 , m ] 的范围内。换句话说,也就是不仅仅由一条死线的限制,可能有多个死线的限制,在 OI 中一般涉及到的是两个死线的问题。以下我们令两条线分别为A : y = x + a A:y=x+a A : y = x + a 和B : y = x + b B:y=x+b B : y = x + b ,默认b < a b<a b < a 即 A 在 B 上方。
简单一点,如果限制只有一个形如y = x + a y=x+a y = x + a ,终点是( n , m ) (n,m) ( n , m ) 那么如何满足,当然还是用我们的反射法,第一次触碰开始反转,终点就是变成关于y = x + a y=x+a y = x + a 对称的点( m − a , n + a ) (m-a,n+a) ( m − a , n + a ) 。那么直接求解自由路即可。
简化问题做完了,我们原命题如何做,对于一条线的容斥,我们相当于去掉了所有包含 A 的串和包含 B 的串。 所以如果对两条线分别跑一次容斥,包含 AB 和 BA 这些串会被去掉两次。
我们要再把先触碰 A 再触碰 B 的方案数加回来,再把先触碰 B 再触碰 A 的方案数加回来。 然后此时包含 ABA 和 BAB 的串又被多加了一次,以此类推。显然碰线的上限次数是确定的,只需要展开有限项即可。
但是难点在于两个都经过的方案,我们考虑两个都经过的方案数怎么算
我们思考能否类似于上面的反射法,将路线反射呢,我们考虑,如果先对y = x + a y=x+a y = x + a 翻折,然后对y = x + ( 2 a − b ) y=x+(2a-b) y = x + ( 2 a − b ) 进行翻折,这个y = x + ( 2 a − b ) y=x+(2a-b) y = x + ( 2 a − b ) 表示 B 直线通过 A 翻折上去后的直线,会变成:
那么方案数就可以通过组合数进行计算了。通过上述我们容斥的过程不断计算即可。注意到每一次反射至少减少一个坐标。所以我们可以在O ( n + m a + b ) O(\dfrac{n+m}{a+b}) O ( a + b n + m ) 的时间复杂度内完成这一操作。
请注意这个特殊的复杂度,这个复杂度可以和一个神秘知识点叫做根号分治结合考察。
2.3 例题骗你呢。
省流:
f ( 1 , 0 / 1 / 2 / … / m ) = 1 f(1,0/1/2/\dots/m)=1 f ( 1 , 0 / 1 / 2 / … / m ) = 1 ,f ( i , j ) = ∑ k = 0 j − 1 f ( i − 1 , k ) f(i,j)=\sum\limits_{k=0}^{j-1}f(i-1,k) f ( i , j ) = k = 0 ∑ j − 1 f ( i − 1 , k ) 。 求∑ i = 0 m f ( n , i ) \sum\limits_{i=0}^m f(n,i) i = 0 ∑ m f ( n , i ) ,其中n , m ≤ 1 0 6 n,m\le 10^6 n , m ≤ 1 0 6 。
先考虑改写 DP 式子,不难改写为f ( i , j ) = f ( i , j − 1 ) + f ( i − 1 , j + 1 ) f(i,j)=f(i,j-1)+f(i-1,j+1) f ( i , j ) = f ( i , j − 1 ) + f ( i − 1 , j + 1 ) ,当j = 0 j=0 j = 0 时f ( i , j ) = f ( i − 1 , j ) + f ( i − 1 , j + 1 ) f(i,j)=f(i-1,j)+f(i-1,j+1) f ( i , j ) = f ( i − 1 , j ) + f ( i − 1 , j + 1 ) 。时间复杂度O ( n 2 ) O(n^2) O ( n 2 ) 。
这种方程是典型的格路计数类方程,考虑将转移图画出来,发现两个移动方向,一个往左上,一个往右。坐标轴一侧变为向上和向右。考虑到左上很难搞,考虑拉伸坐标轴,将左上方向拉伸为和正上方,变为:
把左边的箭头改一改,改成往上后往右即可,根据 DP 性质不难发现两个不能经过的线:y = x + 2 y=x+2 y = x + 2 和y = x − ( m + 1 ) y=x-(m+1) y = x − ( m + 1 ) ,直接反射 DP 即可,以下为参考:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 #include <bits/stdc++.h> #define int long long using namespace std;constexpr int MN=5e6 +15 ,MOD=1e9 +7 ;int n,m,x,y,pw[MN],inv[MN],ans;int ksm (int a,int b) { int ret=1 ; while (b){ if (b&1 ) ret=ret*a%MOD; a=a*a%MOD; b>>=1 ; } return ret; } void flipa () { swap (x,y); x--; y++; } void flipb () { swap (x,y); x+=m+2 ; y-=m+2 ; } void initpw () { pw[0 ]=1 ; for (int i=1 ;i<MN;i++){ pw[i]=pw[i-1 ]*i%MOD; } inv[MN-1 ]=ksm (pw[MN-1 ],MOD-2 ); for (int i=MN-2 ;i>=0 ;i--){ inv[i]=inv[i+1 ]*(i+1 )%MOD; } } int getC (int a,int b) { if (a<b||b<0 ) return 0 ; return pw[a]*inv[b]%MOD*inv[a-b]%MOD; } int calc (int x,int y) { if (x<0 ||y<0 ) return 0 ; return getC (x+y,x); } signed main () { initpw (); cin>>n>>m; x=n+m+1 ,y=n; ans=calc (x,y); while (x>=0 &&y>=0 ){ flipa (); ans=(ans-calc (x,y)+MOD)%MOD; flipb (); ans=(ans+calc (x,y))%MOD; } x=n+m+1 ,y=n; while (x>=0 &&y>=0 ){ flipb (); ans=(ans-calc (x,y)+MOD)%MOD; flipa (); ans=(ans+calc (x,y))%MOD; } cout<<ans; return 0 ; }
首先i = 1 i=1 i = 1 的时候容易有a 1 = 1 a_{1}=1 a 1 = 1 ,对于i > 1 i>1 i > 1 有:
4 S i = a i 2 + 2 a i + 1 4 S i − 1 + 4 a i = a i 2 + 2 a i + 1 4 S i − 1 = a i 2 − 2 a i + 1 4 S i − 1 = ( a i − 1 ) 2 ∴ a i = 1 ± 2 S i − 1 \begin{aligned} 4S_{i} & =a_{i}^2+2a_{i}+1 \\ 4S_{i-1}+4a_{i}&=a_{i}^2+2a_{i}+1 \\ 4S_{i-1}&=a_{i}^2-2a_{i}+1 \\ 4S_{i-1}&=(a_{i}-1)^2 \\ \therefore a_{i}&=1\pm 2\sqrt{S_{i-1}} \end{aligned} 4 S i 4 S i − 1 + 4 a i 4 S i − 1 4 S i − 1 ∴ a i = a i 2 + 2 a i + 1 = a i 2 + 2 a i + 1 = a i 2 − 2 a i + 1 = ( a i − 1 ) 2 = 1 ± 2 S i − 1
注意到S i = S i − 1 + a i = S i − 1 ± 2 S i − 1 + 1 = ( S i − 1 ± 1 ) 2 S_{i}=S_{i-1}+a_{i}=S_{i-1}\pm 2\sqrt{S_{i-1}}+1=(\sqrt{S_{i-1}}\pm 1)^2 S i = S i − 1 + a i = S i − 1 ± 2 S i − 1 + 1 = ( S i − 1 ± 1 ) 2 。设x i = S i x_{i}=\sqrt{S_{i}} x i = S i ,那么拆括号后会变号,那么b 1 = 1 b_{1}=1 b 1 = 1 ,b i = b i − 1 = ± 1 b_i=b_{i-1}=\pm 1 b i = b i − 1 = ± 1 。
考虑值域限制:
若b i = b i − 1 + 1 b_{i}=b_{i-1}+1 b i = b i − 1 + 1 ,那么要求a i = 1 + 2 b i − 1 = 2 b i − 1 a_{i}=1+2b_{i-1}=2b_{i}-1 a i = 1 + 2 b i − 1 = 2 b i − 1 ,即b i ≤ m + 1 2 b_{i}\le \dfrac{m+1}{2} b i ≤ 2 m + 1 。 若b i = b i − 1 − 1 b_{i}=b_{i-1}-1 b i = b i − 1 − 1 ,那么显然b i ≥ 0 b_{i}\ge 0 b i ≥ 0 。 相当于在平面直角坐标系上每一次向右上或右下走一步,初始在( 1 , 1 ) (1,1) ( 1 , 1 ) ,走n − 1 n-1 n − 1 步后要求纵坐标在[ 0 , m + 1 2 ] [0,\dfrac{m+1}{2}] [ 0 , 2 m + 1 ] 范围内的方案数,反射容斥即可。
先考虑发掘一些性质:
每一列至多一个障碍。 障碍会导致方向出现三种选择情况:上下、只上、只下。不可能出现无法选择的情况,因为每一列至多一个障碍。 当我们走到y = m y=m y = m 的时候无敌,因为a i ∈ [ 1 , m ] a_{i}\in [1,m] a i ∈ [ 1 , m ] ,可以一直往上走。 我们考虑给定障碍的点,即给定a a a 我们应该如何判定是否合法,有一个想法就是我们贪心的能往上走就尽量往上走到y = m y=m y = m 的位置。因为根据性质 2 障碍只可能限制至多一个方向的前进,分类讨论不难有往上走是最优解。
我们现在有了决策,现在我们考虑如何计数上述合法的a a a 。考虑 DP,注意到决策只与当前所处的最后一个位置纵坐标有关,故设f ( i , j ) f(i,j) f ( i , j ) 表示决策到第i i i 个数,最后一个的位置纵坐标为j j j 的a a a 方案数,转移分类讨论有:
f ( i + 1 , j + 1 ) ← ( m + 1 ) f ( i , j ) f(i+1,j+1)\leftarrow (m+1)f(i,j) f ( i + 1 , j + 1 ) ← ( m + 1 ) f ( i , j ) ;f ( i + 1 , j − 1 ) ← f ( i , j ) f(i+1,j-1)\leftarrow f(i,j) f ( i + 1 , j − 1 ) ← f ( i , j ) ,当且仅当j < m j<m j < m 。f ( i + 1 , m ) ← f ( i , m ) ⋅ m f(i+1,m)\leftarrow f(i,m)\cdot m f ( i + 1 , m ) ← f ( i , m ) ⋅ m 。转移是O ( n m ) O(nm) O ( n m ) 的就很难泵,考虑优化,发现这玩意是一个O ( 1 ) O(1) O ( 1 ) 递推式子,而且发现这玩意及其类似格路计数问题。考虑转化问题,我们从( 0 , b 0 ) (0,b_{0}) ( 0 , b 0 ) 开始走,每次可以选择右上和右下走,我们有两个限制,一个是碰到y = − 1 y=-1 y = − 1 就寄了,一个是碰到y = m y=m y = m 的时候就结束了。显然我们不难发现这是双线限制问题,可以考虑反射容斥,但是问题在于反射容斥双线是死线是不能碰,这里出现的是一个死线一个活线。
我们可以考虑枚举结束位置( i , m ) (i,m) ( i , m ) ,在这个位置之前都不能碰到y = m y=m y = m 不然就非法,这样就能套上反射容斥了。特别的,枚举( n , i ) (n,i) ( n , i ) 作为结束位置,这个时候y = m y=m y = m 就全部都是死线。然后就可以做了,具体实现由于我不会右上右下的反射容斥,我们可以考虑反向旋转 45 度给回归到往右走和往上走,这样就可以套上 P3266 的做法了。
等会!我们还没有处理 DP 中的系数呢,首先将( i , m ) (i,m) ( i , m ) 和( n , i ) (n,i) ( n , i ) 旋转之后变为( i − m + b 0 2 , i + m − b 0 2 − 1 ) (\frac{i-m+b_{0}}{2},\frac{i+m-b_{0}}{2}-1) ( 2 i − m + b 0 , 2 i + m − b 0 − 1 ) 和( n − i 2 , n + i 2 ) (\frac{n-i}{2},\frac{n+i}{2}) ( 2 n − i , 2 n + i ) 。对于( i , m ) (i,m) ( i , m ) 求解的我们乘上( m − 1 ) i + m − b 0 2 m n − i (m-1)^{\frac{i+m-b_{0}}{2}}m^{n-i} ( m − 1 ) 2 i + m − b 0 m n − i 的系数,后面乘上( m − 1 ) n + i 2 (m-1)^{\frac{n+i}{2}} ( m − 1 ) 2 n + i 即可。
搞笑的,这玩意是O ( n 2 m + n ) O(\dfrac{n^2}{m}+n) O ( m n 2 + n ) 的,但是注意到我们有两个做法,一个是O ( n m ) O(nm) O ( n m ) ,一个是O ( n 2 m + n ) O(\dfrac{n^2}{m}+n) O ( m n 2 + n ) 的,直接考虑根号分治,设定阈值B B B ,满足m ≤ B m\le B m ≤ B 用 DP,否则用计数,时间复杂度O ( n n ) O(n\sqrt{n}) O ( n n ) 。
番外:CatPTG 告诉我说不能用 P3266 的维护方法给我了个直接维护右上右下。我真信了,我调不出来然后我去自己写旋转坐标系的出来了搞笑了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 #include <bits/stdc++.h> #define int long long #define pir pair<int,int> using namespace std;constexpr int MN=5e6 +15 ,MM=2e5 +15 ,MOD=998244353 ;int pw[MN],inv[MN],n,m,b0,ppm[MN],ppm1[MN];int ksm (int a,int b) { int ret=1 ; while (b){ if (b&1 ) ret=ret*a%MOD; a=a*a%MOD; b>>=1 ; } return ret; } void initpw () { pw[0 ]=1 ; for (int i=1 ;i<MN;i++){ pw[i]=pw[i-1 ]*i%MOD; } inv[MN-1 ]=ksm (pw[MN-1 ],MOD-2 ); for (int i=MN-2 ;i>=0 ;i--){ inv[i]=inv[i+1 ]*(i+1 )%MOD; } } int getC (int a,int b) { if (a<b||b<0 ) return 0 ; return pw[a]*inv[b]%MOD*inv[a-b]%MOD; } namespace SUB1{ constexpr int MQ=3520 ; int f[2 ][MQ],ret; void init () { ret=0 ; for (int i=0 ;i<=m;i++){ f[0 ][i]=0 ; } } void initdp (int f[]) { for (int i=0 ;i<=m;i++) f[i]=0 ; } int solve () { init (); f[0 ][b0]=1 ; int now=0 ,nxt=1 ; for (int i=0 ;i<n;i++,nxt^=1 ,now^=1 ){ initdp (f[nxt]); for (int j=0 ;j<m;j++){ if (!f[now][j]) continue ; if (j){ f[nxt][j-1 ]=(f[nxt][j-1 ]+f[now][j])%MOD; } if (j+1 <m){ f[nxt][j+1 ]=(f[nxt][j+1 ]+f[now][j])%MOD; }else ret=(ret+f[now][j]%MOD*(m-1 )%MOD*ppm[n-i-1 ]%MOD*ppm1[(i+j-b0)/2 ]%MOD)%MOD; } } for (int i=0 ;i<m;i++){ if (f[now][i]){ ret=(ret+f[now][i]*ppm1[(i+n-b0)/2 ]%MOD)%MOD; } } return ret; } } namespace SUB2{ void flip (pir &x,int k) { swap (x.first,x.second); x.first-=k; x.second+=k; } int calc (pir x) { if (x.first<0 ||x.second<0 ) return 0 ; return getC (x.first+x.second,x.first); } int sol (int x,int y,int fl,int fr) { pir pos=pir (x,y); int ret=calc (pos); while (pos.first>=0 &&pos.second>=0 ){ flip (pos,fl); ret=(ret-calc (pos)+MOD)%MOD; flip (pos,fr); ret=(ret+calc (pos))%MOD; } pos=pir (x,y); while (pos.first>=0 &&pos.second>=0 ){ flip (pos,fr); ret=(ret-calc (pos)+MOD)%MOD; flip (pos,fl); ret=(ret+calc (pos))%MOD; } return ret; } int solve () { int x=b0,y=0 ,ret=ksm (m,n); for (int i=b0;i<n;i+=2 ,x++,y++){ ret=(ret-sol (x,y,m-b0,-1 -b0)*ppm[n-i-1 ]%MOD*ppm1[y]%MOD+MOD)%MOD; } return ret; } } void init () { ppm[0 ]=ppm1[0 ]=1 ; for (int i=1 ;i<=n;i++){ ppm[i]=ppm[i-1 ]*m%MOD; ppm1[i]=ppm1[i-1 ]*(m-1 )%MOD; } } void solve () { cin>>n>>m>>b0; if (b0>=m){ cout<<ksm (m,n)<<'\n' ; return ; } init (); if (m*m<=n){ cout<<SUB1::solve ()<<'\n' ; }else cout<<SUB2::solve ()<<'\n' ; } signed main () { initpw (); int T; cin>>T; while (T--){ solve (); } return 0 ; }
2.4 一点小感想反射容斥源于卡特兰数的组合意义技巧,即我们一开始说的反射法。是反射法和容斥法的有机结合,更多地运用了容斥思想,用满足部分条件的集合的交并刻画我们 需要的集合。
在遇到有限制的格路计数问题中,我们可以通过反射容斥来进行操作。
至于 Dyck 路,先咕咕咕吧。
3. 参考图源:
参考: