简单组合计数

简单组合计数

组合计数基础

几个原理:

1.加法原理:若完成一件事有\(n\)类不同的方法,第\(i\)类方法有\(a_i\)种方法,且这些方法互不重合则完成这件事共有\(\sum_{i=1}^na_i\)种方法

2.乘法原理:若完成一件事有\(n\)个不同的步骤,每个步骤有\(a_i\)种完成方法,且互不干扰,则完成该件事共有\(\prod_{i=1}^na_i\)种方法

几个定义:

1.排列数:从\(n\)个不同的数里依次 取出\(m\)个,产生的不同排列数量为:

\[A_n^m(\text{或}P_n^m)=\prod_{i=n-m+1}^n i=\frac{n!}{(n-m)!} \]

注意是\(m\)在上

2.组合数:从\(n\)个不同的数里取出\(m\)个组成一个集合(不考虑顺序,只要元素到位),产生的不同集合数量为:

\[C_n^m=\frac{A_n^m}{m!}=\frac{n!}{m!(n-m)!} \]

性质:

1.\(C_n^m=C_n^{n-m}\)

2.\(C_n^m=C_{n-1}^m+C_{n-1}^{m-1}\)      重要

3.\(\sum_{i=0}^nC_n^i=2^n\)

4.\(\sum_{k=0}^n(-1)^kC_n^k=0\)

5.\(C_{m+n}^k=\sum_{j=0}^k(C_m^{k-j}\times C_n^j)\)

6.\(C^{k+1}_ {n+1} =\sum_{i=k}^nC_i^k\)

7.Lucas定理:若\(p\)是质数,则对于任意整数\(1\le m\le n\)

\[C_n^m\equiv C^{m \bmod p}_ {n\bmod p}\times C^{\lfloor \frac{m}{p} \rfloor}_ {\lfloor \frac{n}{p} \rfloor} (\bmod p) \]

这个注意代码实现的时候得递归实现

二项式定理

\[(a+b)^n=\sum_{k=0}^nC_n^ka^kb^{n-k} \]

多项式定理

\(n\)是正整数,则对于所有的\(x_i\) \((i\in [1,t])\)

\[(\sum_{i=1}^tx_i)^n=\sum_{\left(\sum_{p=1}^tn_p\right)=n}\left(\frac{n!}{\prod_{k=1}^t(n_k!)}\right)\prod_{j=1}^tx_j^{n_j} \]

这里把\(n_i\)的所有组合枚举出来即可

多重集的排列数

多重集是允许包含重复元素的集合,设集合\(\mathbb{S}={\lbrace n_1·a_1,n_2·a_2,n_3·a_3…n_k·a_k\rbrace}\)是由\(n_1\)\(a_1\)……组成的多重集,\(n=\sum_{i=1}^kn_i\)则它的全排列个数为:

\[\frac{n!}{\prod_{k=1}^m(n_k!)} \]

多重集的组合数

设集合\(\mathbb{S}={\lbrace n_1·a_1,n_2·a_2,n_3·a_3…n_k·a_k\rbrace}\)是由\(n_1\)\(a_1\)……组成的多重集,\(n=\sum_{i=1}^kn_i\)
则从该集合中选出\(r(r\le n)\)个数组成的不同的多重集数量为:

\[C_{k+r-1}^{k-1}-\sum_{i=1}^kC^{k-1}_ {k+r-n_i-2}+\sum_{1\le i<j \le n}C_{k+r-n_i-n_j-2}^{k-1}-…+(-1)^kC_{k+r-n-k-1}^{k-1} \]

Catlan数

定义

\[Cat_n=C_{2n}^n-C_{2n}^{n-1}=\frac{C_{2n}^n}{n+1} \]

与其有关的问题:

1.给定n个左括号和n个右括号组成合法的括号序列的个数为\(Cat_n\)

2.\(1\sim n\)依次进栈,合法的出栈序列个数为\(Cat_n\)

3.n个节点组成的不同二叉树的数量是\(Cat_n\)

4.在平面直角坐标系中每一步只能向上或者向右走,从点\((0,0)\)到点\((n,n)\)并且除了两个端点之外路线上没有一个点经过直线y=x,这样的路径条数一共有\(2Cat_{n-1}\)

几个结论:

1.将一个长度为n的环变作n个自环至少要将节点交换n-1次

2.设\(F_n\)表示用最少步骤将一个长度为n的环变作n个自环的操作方案数,有:\(F_n=n^{n-2}\)

3.遇到序列上的错位,需要交换位置还原的问题可以往图论方向想

exlucas

用途:快速求出以下式子的值(p不一定是质数)

\[C_n^m \bmod p \]

遇到这种情况,我们可以将p进行算术基本定理的质因数分解。设\(p=\prod_{i=1}^kp_i^{c_i}\),我们设\(a_i=C_n^m \bmod p_i^{c_i}\)

这样我们就把问题简化成了:

\[\left\{ \begin{aligned} x \bmod& p_1^{c_1}=a_1 \\ x \bmod& p_2^{c_2}=a_2 \\ x \bmod& p_3^{c_3}=a_3 \\ &\vdots \\ x \bmod& p_k^{c_k}=a_k \\ \end{aligned} \right. \]

这个一次同余式组直接用中国剩余定理\(CRT\)求即可

问题在于如何求出\(C_n^m \bmod p_i^{c_i}\)

先来看简化版扩展卢卡斯:

古代猪文

题意:给定整数\(n,q(n,q\le 10^9)\)计算

\[q^{\sum_{d|n}C_n^d } \bmod 999911659 \]

因为999911659是一个质数,由扩欧得:

原式等价于

\[q^{\sum_{d|n}C_n^d \bmod 999911658} \bmod 999911659 \]

所以本题关键在于计算\(\sum_{d|n}C_n^d \bmod 999911658\)

按照扩展卢卡斯的思路,我们将999911658分解质因数发现:\(999911658=2\times 3\times 4679 \times 35617\)

我们枚举n的约数d,分成四个,运用Lucas定理分别计算组合数\(C_n^d\)对这四个质因数的模,求出\(\sum_{d|n}C_n^d\)模这四个质数的值,记作\(a_1,a_2,a_3,a_4\)

当然对于质数的逆元啥的自己预处理一下就可以

现在我们的问题就是求解同余方程组

\[\left\{ \begin{aligned} x \bmod& 2&=&a_1 \\ x \bmod& 3&=&a_2 \\ x \bmod& 4679&=&a_3 \\ x \bmod& 35617&=&a_4 \\ \end{aligned} \right. \]

然后用快速幂进行计算即可

#define int long long 
int mod=999911659,p[4]={2,3,4679,35617},n,m,cnt,ans,q;
int ys[40000],jc[40000],a[4],x,y,t;
int power(int a,int b,int p){
    int ans=1;
    while(b){
    	if(b&1)ans=ans*a%p;
    	a=a*a%p;
    	b>>=1;
	}
    return ans;
}
int C(int n,int m,int p){
    return n<m?0:jc[n]*power(jc[m]*jc[n-m],p-2,p)%p;
}
int lucas(int n,int m,int p){
    return m?C(n%p,m%p,p)*lucas(n/p,m/p,p)%p:1;
}
int exgcd(int a,int b,int &x,int &y){
    if(!b){
    	x=1,y=0;
    	return a;
	}
	int d=exgcd(b,a%b,x,y);
	int t=x;
	x=y,y=t-(a/b)*y;
	return d;
}
signed main(){
    scanf("%lld%lld",&n,&q);
    if(q%mod==0){
        puts("0");
        return 0;
    }
    jc[0]=1;
    m=sqrt(n);
    for(int i=1;i<=m;i++){
        if(n%i==0){
			ys[++cnt]=i,ys[++cnt]=n/i;
		}
	}
    cnt-=ys[cnt-1]==ys[cnt];
    for(int i=0;i<4;i++){
        for(int j=1;j<p[i];j++){
			jc[j]=jc[j-1]*j%p[i];
		}
        for(int j=1;j<=cnt;j++){
			a[i]=(a[i]+lucas(n,ys[j],p[i]))%p[i];
		}
        exgcd((mod-1)/p[i],p[i],x,y);
        ans=(ans+a[i]*(x%p[i]+p[i])*(mod-1)/p[i])%(mod-1);
    }
    printf("%lld\n",power(q,ans,mod));
    return 0;
}
 

这也就是所有的\(c_i=1\)时的简化情况

posted @ 2022-11-30 22:23  spdarkle  阅读(89)  评论(0编辑  收藏  举报