组合数学(1)
若 p p p是质数,则对于任意整数 1 ≤ m ≤ n 1\leq m \leq n 1≤m≤n,有以下关系成立
C n m = C n m o d p m m o d p × C n / p m / p ( m o d p ) C_n^m=C_{n \bmod{p}}^{m\bmod{p}}\times C_{n/p}^{m/p}\pmod{p} Cnm=Cnmodpmmodp×Cn/pm/p(modp)
给定 n n n个0和 n n n个1,按照某种顺序排成长度为 2 n 2n 2n的序列,满足任意前缀中0的个数都不少于1的个数的序列数量为:
h ( n ) = C 2 n n n + 1 h(n)=\frac{C_{2n}^n}{n+1} h(n)=n+1C2nn
并且满足递推公式
h ( n ) = h ( n − 1 ) × 4 n − 2 n + 1 h(n)=h(n-1)\times \frac{4n-2}{n+1} h(n)=h(n−1)×n+14n−2
与Catalan数有关的问题
- n n n个左括号和 n n n个右括号组成的合法括号序列数量为 h ( n ) h(n) h(n)
- 1 , 2 , … , n 1,2,\dots, n 1,2,…,n经过一个栈,形成合法的栈序列为 h ( n ) h(n) h(n)
- n n n个节点构成不同的二叉树的数量为 h ( n ) h(n) h(n)
- 在平面直角坐标系上,每一步只能向上或者向右走,从 ( 0 , 0 ) (0, 0) (0,0)走到 ( n , n ) (n, n) (n,n)并且除两个端点外不接触直线 y = x y=x y=x的路线数量为 2 h ( n ) 2h(n) 2h(n)
若整数 b , p b, p b,p互质,并且 b ∣ p b\mid p b∣p,则存在一个整数 x x x,使得 a b ≡ a ∗ x ( m o d p ) \frac{a}{b}\equiv a*x\pmod{p} ba≡a∗x(modp),称 x x x为 b b b的模 p p p乘法逆元,记为 b − 1 ( m o d p ) b^{-1}\pmod{p} b−1(modp). b<p
如果 p p p为质数,并且 b < p b
题目链接
在图示参数为 a , b , c , d a, b, c, d a,b,c,d的网格棋盘中,放上 K K K个相互不攻击的车,求方案的总数
将棋盘做如图所示的切分
枚举在左上部分摆 i i i个车,在下半部分摆 K − i K-i K−i个车,进行组合计数,可以推出方案总数为
∑ i = 0 k C b i P a i C a k − i P a + c − i k − i \sum_{i=0}^kC_b^iP_a^iC_a^{k-i}P_{a+c-i}^{k-i} i=0∑kCbiPaiCak−iPa+c−ik−i
#include
#include
using namespace std;
typedef long long LL;
const int N=2005;
const int mod=100003;
int fact[N], infact[N];
int qmi(int a, int k){
int ans=1;
while(k){
if(k&0x01) ans=(LL)ans*a%mod;
a=(LL)a*a%mod;
k>>=1;
}
return ans;
}
int C(int a, int b){
if(a<b) return 0;
return (LL)fact[a]*infact[a-b]*infact[b]%mod;
}
int P(int a, int b){
if(a<b) return 0;
return (LL)fact[a]*infact[a-b]%mod;
}
int main(){
fact[0]=infact[0]=1;
for(int i=1; i<N; ++i){
fact[i]=(LL)fact[i-1]*i%mod;
infact[i]=(LL)infact[i-1]*qmi(i, mod-2)%mod;
}
int a, b, c, d, k;
cin>>a>>b>>c>>d>>k;
LL ans=0;
for(int i=0; i<=k; ++i){
ans=(ans%mod+(LL)C(b, i)*P(a, i)%mod*(LL)C(d, k-i)*P(a+c-i, k-i)%mod)%mod;
}
cout<<ans<<endl;
return 0;
}
题目链接
在一个 n × m n\times m n×m的网格,计算三个点都在格点上的三角形的数量
分情况进行组合计数,考虑三点共线不能组成三角形的情况
#include
#include
using namespace std;
typedef long long LL;
inline int gcd(int a, int b){return b? gcd(b, a%b): a;}
inline LL C(int n){return (LL)n*(n-1)*(n-2)/6;}
int main(){
int n, m;
cin>>n>>m;
++n, ++m;
LL ans=C(n*m)-(LL)n*C(m)-(LL)m*C(n);
for(int i=1; i<=n; ++i)
for(int j=1; j<=m; ++j)
ans-=2ll*(gcd(i, j)-1)*(n-i)*(m-j);
cout<<ans<<endl;
return 0;
}
题目链接
统计长度在 1 ∼ N 1\sim N 1∼N之间, 元素大小都在 L L L到 R R R之间的单调不下降序列的数量
将 L ∼ R L\sim R L∼R之间的数映射到 0 ∼ R − L 0\sim R-L 0∼R−L区间,使得每个数满足条件
0 ≤ a 1 ≤ a 2 ≤ ⋯ ≤ a k ≤ R − L 0\leq a_1\leq a_2\leq\dots\leq a_k\leq R-L 0≤a1≤a2≤⋯≤ak≤R−L
不妨做如下映射
{ x 1 = a 1 x 2 = a 2 − a 1 x 3 = a 3 − a 2 ⋮ x k = a k − a k − 1 ∑ i = 1 k x i = a k ≤ R − L \left\{ \begin{aligned} &x_1=a_1\\ &x_2=a_2-a_1\\ &x_3=a_3-a_2\\ &\vdots \\ &x_k=a_k-a_{k-1} \end{aligned} \right.\\ \sum_{i=1}^k x_i=a_k\leq R-L ⎩⎪⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎪⎧x1=a1x2=a2−a1x3=a3−a2⋮xk=ak−ak−1i=1∑kxi=ak≤R−L
转化为找到一组 x i x_i xi使得以下不等式成立,求方案数问题
x 1 + x 2 + ⋯ + x k ≤ R − L x i ≥ 0 \begin{aligned} &x_1+x_2+\dots+x_k\leq R-L\\ &x_i\geq 0 \end{aligned} x1+x2+⋯+xk≤R−Lxi≥0
使用隔板法可以推算出方案数为 C R − L + k k C_{R-L+k}^k CR−L+kk,对 k k k在 1 ∼ N 1\sim N 1∼N之间进行枚举,令 M = R − L M=R-L M=R−L,根据递推公式 C a b = C a − 1 b + C a − 1 b − 1 C_a^b=C_{a-1}^b+C_{a-1}^{b-1} Cab=Ca−1b+Ca−1b−1进行化简
∑ k = 1 N = C R − L + k k = C M + k k = C M + k M = C M + 1 M + C M + 2 M + ⋯ + C M + N M = ( C M + 1 M + 1 + C M + 1 M ) + C M + 2 M + ⋯ + C M + N M − 1 = ( C M + 2 M + 1 + C M + 2 M ) + ⋯ + C M + N M − 1 = C M + N + 1 M + 1 − 1 = C R + L + N + 1 R − L + 1 − 1 \begin{aligned} \sum_{k=1}^N&=C_{R-L+k}^k=C_{M+k}^k=C_{M+k}^M \\ &=C_{M+1}^M+C_{M+2}^M+\dots+C_{M+N}^M \\ &=(C_{M+1}^{M+1}+C_{M+1}^M)+C_{M+2}^M+\dots+C_{M+N}^M-1\\ &=(C_{M+2}^{M+1}+C_{M+2}^M)+\dots+C_{M+N}^M-1\\ &=C_{M+N+1}^{M+1}-1\\ &=C_{R+L+N+1}^{R-L+1}-1 \end{aligned} k=1∑N=CR−L+kk=CM+kk=CM+kM=CM+1M+CM+2M+⋯+CM+NM=(CM+1M+1+CM+1M)+CM+2M+⋯+CM+NM−1=(CM+2M+1+CM+2M)+⋯+CM+NM−1=CM+N+1M+1−1=CR+L+N+1R−L+1−1
组合数算法使用Lucas
定理,时间复杂度为 O ( P log N P ) \mathcal{O}(P\log_N^P) O(PlogNP).
#include
#include
using namespace std;
typedef long long LL;
const int mod=1000003;
int qmi(LL a, int k){
LL ans=1;
while(k){
if(k&0x01) ans=ans*a%mod;
a=a*a%mod;
k>>=1;
}
return ans;
}
int C(int a, int b){ // 计算组合数C_a^b
if(a<b) return 0;
int d=1, u=1;
for(int i=a, j=1; j<=b; i--, j++){
u=(LL)u*i%mod;
d=(LL)d*j%mod;
}
return (LL)u*qmi(d, mod-2)%mod;
}
int Lucas(int a, int b){ // Lucas定理
if(a<mod && b<mod) return C(a, b);
else return (LL)Lucas(a/mod, b/mod)*C(a%mod, b%mod)%mod;
}
int main(){
int T;
cin>>T;
while(T--){
int n, l, r;
cin>>n>>l>>r;
cout<<(Lucas(r-l+n+1, r-l+1)-1+mod)%mod<<endl;
}
return 0;
}