首页 > 其他分享 >组合数学

组合数学

时间:2024-07-26 19:52:10浏览次数:5  
标签:frac 组合 int sum times vmatrix 数学 ll

前置芝士:

平方和公式: \(1^2+2^2+\cdots+n^2=\frac{n(n+1)(2n+1)}{6}\)

概念与计数:

基本计数原理:

分类计算加法原理,分布计算乘法原理。

简单容斥与摩根定理:

  • \(\begin{vmatrix}A\cup B\end{vmatrix}=\begin{vmatrix}A\end{vmatrix}+\begin{vmatrix}B\end{vmatrix}-\begin{vmatrix}A\cap B\end{vmatrix}\)
  • \(\begin{vmatrix}A\cup B\cup C\end{vmatrix}=\begin{vmatrix}A\end{vmatrix}+\begin{vmatrix}B\end{vmatrix}+\begin{vmatrix}C\end{vmatrix}-\begin{vmatrix}A\cap B\end{vmatrix}-\begin{vmatrix}A\cap C\end{vmatrix}-\begin{vmatrix}C\cap B\end{vmatrix}+\begin{vmatrix}A\cap B\cap C\end{vmatrix}\)

\[\left| \bigcup_{i=1}^n A_i\right|=\sum_{\varnothing\not={\rm J}\subseteq\{1,\cdots,n\}}(-1)^{\begin{vmatrix}{\rm J}\end{vmatrix}+1}\left| \bigcap_{{\rm j}\in{\rm J}} A_j\right|\\ \]

代码可以利用 \(dfs\) 或状压实现

 

摩根定理: 交集的补等于补集的并,并集的补等于补集的交。

即 \(\overline{A\cup B}=\overline{A}\cap\overline{B}\) \(\overline{A\cap B}=\overline{A}\cup\overline{B}\)

\[\left| \bigcap_{i=1}^n \overline{A_i}\right|=\sum_{{\rm J}\subseteq\{1,\cdots,n\}}(-1)^{\begin{vmatrix}{\rm J}\end{vmatrix}}\left| \bigcap_{{\rm j}\in{\rm J}} A_j\right|\\ \left| \bigcap_{i=1}^n A_i\right|=\sum_{{\rm J}\subseteq\{1,\cdots,n\}}(-1)^{\begin{vmatrix}{\rm J}\end{vmatrix}}\left| \bigcap_{{\rm j}\in{\rm J}} \overline{A_j}\right|\\ \]

 

组合计数:

排列数:

从 \(n\) 个不同元素中依次取出 \(m\) 个元素排成一列,产生的不同排列的数量为:(取 \(m\) 个,将 \(m\) 个排序)

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

组合数:

从 \(n\) 个不同元素中取出 \(m\) 个组成一个集合(不考虑顺序),产生的不同集合的数量为:

\[C_n^m= \begin{pmatrix} n\\m \end{pmatrix} =\frac{n!}{m!(n-m)!} \]

 

性质:

  • \(A_n^k=C_n^k\times k!\)
  • \(C_n^m=C_n^{n-m}\)
  • \(C_n^m=C_{n-1}^m+C_{n-1}^{m-1}\) (杨辉三角)
  • \(\frac{k}{n}\times C_n^k=C_{n-1}^{k-1}\)
  • \(\sum_{i=0}^nC_n^i=2^n\)
  • \(\sum_{i=0}^n(-1)^iC_n^i=0\)

 

组合数求解:

单个组合数 \(O(n)\)

代码
int C(int n, int k) {
	int p = 1, q = 1;
    for (int i = n - k + 1; i <= n; ++i)
        p *= i;
    for (int i = 1; i <= k; ++i)
        q *= i;
    return p / q;
}

求 \(C_i^j(i\in[0,n-1],j\in[1,i])\) 递推 \(O(n^2)\)

代码
for (int i = 0; i < n; ++i) {
    C[i][0] = 1;
    for (int j = 1; j <= i; ++j) {
        C[i][j] = C[i - 1][j - 1] + C[i - 1][j];
    }
}

 

二项式定理:

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

 

高精组合数:

求 \(C_n^m\) 的值,结果可能很大,没有模数。

\({\rm sol}:\)
\(C_n^m=\frac{n!}{m!\times (n-m)!}\)
$\therefore $ 所以可以对 \(n!\) 阶乘进行质因数分解为 \(\prod p_i^{c_i}\) (方法在基础数论博客里)
然后对 \(m!\) 和 \((n-m)!\) 分别进行质因数分解,每次使 \(c_i\) 减一。
最后统计结果。
时间复杂度 \(O(n)\) (高精另算)

代码
#include <iostream>
#include <algorithm>
#include <vector>

using namespace std;


const int N = 5010;

int primes[N], cnt;
int sum[N];
bool st[N];


void get_primes(int n)
{
    for (int i = 2; i <= n; i ++ )
    {
        if (!st[i]) primes[cnt ++ ] = i;
        for (int j = 0; primes[j] <= n / i; j ++ )
        {
            st[primes[j] * i] = true;
            if (i % primes[j] == 0) break;
        }
    }
}


int get(int n, int p) // 求n!中 质因子 p 需要累乘的次数 
{
    int res = 0;
    while (n)
    {
        res += n / p;
        n /= p;
    }
    return res;
}


vector<int> mul(vector<int> a, int b)
{
    vector<int> c;
    int t = 0;
    for (int i = 0; i < a.size(); i ++ )
    {
        t += a[i] * b;
        c.push_back(t % 10);
        t /= 10;
    }
    while (t)
    {
        c.push_back(t % 10);
        t /= 10;
    }
    return c;
}


int main()
{
    int a, b;
    cin >> a >> b;

    get_primes(a);//用欧拉筛筛出 [1~a] 范围内的质数 

    for (int i = 0; i < cnt; i ++ )
    {
        int p = primes[i];
        sum[i] = get(a, p) - get(a - b, p) - get(b, p); // C(a,b) 中第 i 个质数需要累乘的次数 
    }

    vector<int> res;
    res.push_back(1);

    for (int i = 0; i < cnt; i ++ ) // 枚举质因子 
        for (int j = 0; j < sum[i]; j ++ ) // 枚举当前质因子的个数 
            res = mul(res, primes[i]); // 做高精度乘低精度 

    for (int i = res.size() - 1; i >= 0; i -- ) printf("%d", res[i]);
    puts("");

    return 0;
}

 

多重集排列数:

\(k\) 种元素,有 \(n_1\) 个 \(a_1\) , \(n_2\) 个 \(a_2\) ,\(\cdots\) , \(n_k\) 个 \(a_k\) 。有多少种排列方案。

\[\frac{(n_1+n_2+\cdots+n_k)!}{n_1!\times n_2!\times\cdots\times n_k!} \]

多重集组合数:

1.
\(k\) 种元素,有 \(n_1\) 个 \(a_1\) , \(n_2\) 个 \(a_2\) ,\(\cdots\) , \(n_k\) 个 \(a_k\) 。从中取出 \(r\) ( \(\forall i\in[1,k],r\leq n_i\) )个的方案数。

设 \(r\) 个元素构成的集合是 \(\{x_1·a_1,x_2·a_2,\cdots,x_k·a_k\}\)
\(\therefore x_1+x_2+\cdots+x_k=r\) 且 \(x_i\geq 0\) (隔板法)
答案为 \(C_{r+k-1}^{k-1}\)

2.
\(k\) 种元素,有 \(n_1\) 个 \(a_1\) , \(n_2\) 个 \(a_2\) ,\(\cdots\) , \(n_k\) 个 \(a_k\) 。从中取出 \(r\) ( \(r\leq \sum_{i=1}^kn_i\) )个的方案数为:

\[C_{k+r-1}^{k-1}-\sum_{i=1}^kC_{k+r-n_i-2}^{k-1}+\sum_{1\leq i<j\leq k}C_{k+r-n_i-n_j-3}^{k-1}-\cdots+(-1)^kC_{k+r-\sum_{i=1}^kn_i-(k+1)}^{k-1} \]

若先不考虑 \(n_i\) 的限制,从 \(S=\{\infty · a_1,\infty · a_2,\cdots,\infty · a_k\}\) 中取出 \(r\) 个元素,则方案数为 \(C_{k+r-1}^{k-1}\)
设 \(S_i\) 表示包含至少 \(n_i+1\) 个 \(a_i\) 的多重集,即从 \(S\) 中先取出 \(n_i+1\) 个 \(a_i\), 然后再任选 \(r-n_i-1\) 个元素构成 \(S_i\) ,可得不同的 \(S_i\) 的数量为 \(C_{k+r-n_i-2}^{k-1}\)
那么进一步思考,从 \(S\) 中先取出 \(n_i+1\) 个 \(a_i\) 和 \(n_j+1\) 个 \(a_j\) ,然后再任选 \(r-n_i-n_j-2\) 个元素,构成的集合即为 \(S_i\cap S_j\) ,方案数为 \(C_{k+r-n_i-n_j-3}^{k-1}\)
根据容斥原理可得:

\[\left| \bigcup_{i=1}^kS_i \right| =\sum_{i=1}^kC_{k+r-n_i-2}^{k-1}-\sum_{1\leq i<j\leq k}C_{k+r-n_i-n_j-3}^{k-1}+\cdots+(-1)^{k+1}C_{k+r-\sum_{i=1}^kn_i-(k+1)}^{k-1} \]

故答案为 \(C_{k+r-1}^{k-1}-\left|\cup_{i=1}^kS_i\right|\)

板子题Devu and Flowers

代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;

inline ll read(){
	ll s=0,k=1;
	char c=getchar();
	while(c>'9'||c<'0'){
		if(c=='-')k=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9'){
		s=(s<<3)+(s<<1)+(c^48);
		c=getchar();
	}
	return s*k;
}

const int mod=1e9+7;
ll a[25],n,m,ans,inv[25];

ll ksm(ll a,ll b){
	ll t=1;
	for(;b;b>>=1,a=a*a%mod)
		if(b&1) t=t*a%mod;
	return t;
}

ll C(ll y,ll x){
	if(x<0||y<0) return 0;
	if(x>y) return 0;
	y%=mod;
	ll ans=1;
	for(int i=0;i<x;i++) ans=ans*(y-i)%mod;
	(ans*=inv[x])%=mod;
	if(y==0) return 1;
	return ans;
}

int main(){
	ll t=1;
	for(int i=1;i<=20;i++) t=t*i%mod;
	inv[20]=ksm(t,mod-2);
	for(int i=19;i>=0;i--) inv[i]=inv[i+1]*(i+1)%mod;
	n=read();m=read();
	for(int i=1;i<=n;i++) a[i]=read();
	for(int j=0;j< 1<<n ;j++){
		if(j==0) ans=(ans+C(n+m-1,n-1))%mod;
		else{
			ll tt=n+m;
			ll s=0;
			for(int i=0;i<n;i++)
				if(j>>i&1){
					s++;
					tt-=a[i+1];
				}
			tt-=s+1;
			if(s&1) ans=(ans-C(tt,n-1))%mod;
			else ans=(ans+C(tt,n-1))%mod;
		}
	}
	printf("%lld\n",(ans+mod)%mod);
	return 0;
}

 

计数技巧:

等价替代(映射):

构造一个映射,将每一种原问题的方案映射为新问题的一种方案,并使答案更容易计算。
例如捆绑法,插空法,隔板法等。

捆绑法: 也成整体法,即若要求若干物品相邻,可以将他们视作一个整体来计数。

插空法: 如果要求若干物品两两不相邻,可以先将其他物品放好,然后将这些物品插入空挡当中进行计数。

隔板法: 将不可区分物品分配问题、不定方程整数解问题转化为插板组合问题。

例: 把 \(n\) 个相同的苹果分给 \(k\) 个人,要求每人至少分到一个的方案数。(求 \(x_1+x_2+x_3+\cdots+x_k=n\) 的整数解,满足 \(x_i\geq 1\))
把 \(n\) 个苹果排成一排,从左到右一次插入 \(k-1\) 块板子,共有 \(n-1\) 个位置可以插入,所以答案为 \(C_{n-1}^{k-1}\) 。

 

改变计数目标:

如果直接按照题意来计数比较困难,可以尝试通过减法、容斥原理等方法转换成容易求的目标。

 

改变枚举顺序:

很多时候,计数题要做的基本上是:在某个范围内枚举元素,计算它们的和。直接按照题意来做显然只能拿到暴力分(x), 我们往往需要安排一个合适的顺序来计算。

例: 求 \(\sum_{i=1}^n\sum_{j=1}^n\max(i,j)\)

\[\sum_{i=1}^n\sum_{j=1}^n\max(i,j)=\sum_{k=1}^nk\times\sum_{i=1}^n\sum_{j=1}^n[\max(i,j)==k]\ \ \ \ \ \ \ \\ =\sum_{k=1}^nk\times(2k-1)\ \ \ \ \\ =\sum_{k=1}^n(2n+1)k-k^2\\ =\frac{4n^3+3n^2-n}{6}\ \ \ \ \ \]

 

例题:

数字求和:

设 \(f(x)\) 为 \(x\) 在十进制下的各位数字之和,例如 \(f(123) = 1+2+3=6\) ,求

\[\sum_{i=0}^{10^n}f(x) \]

答案对 \(10^9+7\) 取模。

考虑除去 \(10^n\) 这一个数字之外的其他数字中:
\(\forall i \in [1,9]\) ,都会在每一位上出现 \(10^{n-1}\) 次。
所以这些数字对答案的贡献就是 \(\sum_{i=1}^9i\times n\times 10^{n-1}=45\times n\times 10^{n-1}\)
故答案为 \(45\times n\times 10^{n-1}+1\)

 

圆盘染色:

\(n\) 块组成一个圆盘,用 \(m\) 种颜色染色,使得相邻两块的颜色不同。求方案数。

考虑如果 \(n\) 的颜色与 \(n-2\) 相同,则将 \(n-2,n-1,n\) 看作一个整体。
那么 \(n-1\) 块有 \(m-1\) 种选择,则方案数为 \((m-1)\times f(n-2)\) ,( \(f(i)\) 表示分成 \(i\) 块的方案数)
考虑如果 \(n\) 的颜色与 \(n-2\) 不同,将 \(n-2,n-1,n\) 看作一个整体。
那么 \(n-1\) 块有 \(m-2\) 种选择,则方案数为 \((m-2)\times f(n-1)\) 。
\(\therefore f(n)=(m-1)\times f(n-2)+(m-2)\times f(n-1)\)
( \(f(1)=0,f(2)=m\times(m-1),f(3)=m\times (m-2)\) )

 

数三角形

给定一个 \(N\times M\) 的网格,请计算三点都在格点上的三角形共有多少个。注意三角形的三点不能共线。

【三角形数量】等于【任选三个点的方案数】减去【三点共线的方案数】
【三点共线的方案数】等于【横着的】加【竖着的】加【斜着的】
【横着的】:\((m+1)\times C_{n+1}^3\)
【竖着的】:\((n+1)\times C_{m+1}^3\)
斜着的直线按斜率正负分为两种,并且这两种的方案数是相等的。因此,我们只需要计算出斜率为正的方案数,再乘以 \(2\) 即可。将核心计算内容——斜率为正的方案数记为 \(ans\) 。
假设网格中 \(AB\) 平行于底边 \(x\) 轴,长度为 \(i\) , \(BC\) 平行于侧边 \(y\) 轴,长度为 \(j\) ,那么 \(AC\) 上的整点数量为 \(\gcd(i,j)-1\) (不算 \(A,C\) 两点)。

\[\therefore ans=\sum_{i=1}^n\sum_{j=1}^m(n-i+1)(m-j+1)(\gcd(i,j)-1) \]

此时还能继续化简:

\[\begin{array}{l} =\sum_{i=1}^n\sum_{j=1}^m(n-i+1)(m-j+1)(\sum_{d\mid \gcd(i,j)}\varphi(d)-1)\\ =\sum_{i=1}^n\sum_{j=1}^m(n-i+1)(m-j+1)\sum_{d\mid\gcd(i,j)}^{d\neq 1}\varphi(d)\\ =\sum_{d=2}^{{\rm min}(n,m)}\varphi(d)\sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}(n-id+1)\sum_{j=1}^{\lfloor\frac{m}{d}\rfloor}(m-jd+1)\\ =\frac{1}{4}\sum_{d=2}^{{\rm min}(n,m)}\varphi(d)(n-d+n\% d+2)\lfloor\frac{n}{d}\rfloor(m-d+m\% d+2)\lfloor\frac{m}{d}\rfloor\\ (等差数列) \end{array} \]

至此可以 \(O(n)\) 求得答案。

 

Counting swaps

给定你一个 \(1∼n\) 的排列 \(p\) ,可进行若干次操作,每次选择两个整数 \(x,y\) ,交换 \(px,py\) 。用最少的操作次数将给定排列变成单调上升的序列 \(1,2,\cdots,n\) ,有多少种方式呢?

对于一个排列 \(p_1,p_2,\cdots,p_n\) ,每一个 \(p_i\) 向 \(i\) 连一条无向边,构成一张由若干环组成的无向图,目标状态即为 \(n\) 个自环。

设 \(f[i]\) 表示一个大小为 \(i\) 的环,在保证交换次数最少的情况下,有多少种方法将其变成目标状态。

每一次交换可以把大小为 \(i\) 的环拆成大小为, \(x,y\) 的两个环(\(x+y=n\)) 。设 \(T(x,y)\) 表示有多少种交换方法可以将一个大小为 \(i\) 的环拆成两个大小分别为 \(x,y\) 的环。可以发现:当 \(x=y\) 时, \(T(x,y)=x\) ,否则 \(T(x,y)=x+y\) 。

\(x\) 环需要 \(x−1\) 次交换达到目标状态,同理 \(y\) 环需要 \(y−1\) 次操作,由于 \(x\) 环和 \(y\) 环的操作互不干扰且可以随意排列,因此得到转移方程:

\[f[i]=\sum_{x+y=i}f[x]\times f[y]\times T(x,y)\times \frac{(i-2)!}{(x-1)!\times (y-1)!} \]

(在打表后可发现 \(f[n]=n^{n-2}\))
假设排列中有 \(k\) 个大小为 \(L_1,L_2,\cdots,L_k\) 的环,则

\[Ans=(\prod_{i=1}^kf[L_i])\times (\frac{(n-k)!}{\prod_{i=1}^k(L_i-1)!}) \]

 

算法:

卡特兰数:

其对应序列为:

\(H_0\) \(H_1\) \(H_2\) \(H_3\) \(H_4\) \(H_5\) \(H_6\) \(H_7\) \(\cdots\) \(H_n\)
1 1 2 5 14 42 132 429 \(\cdots\) \(\frac{C_{2n}^n}{n+1}\)
  • \( H_n\begin{cases} \sum_{i=1}^nH_{i-1}\times H_{n-i}\ n\geq2,n\in\mathbb{N_{+}}\\ 1\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ n=0,1 \end{cases} \)
  • \(H_n=\frac{H_{n-1}\ \times(4n-2)}{n+1}\)
  • \(H_n=C_{2n}^n-C_{2n}^{n-1}\)

 

对于一下问题属于 Catalan 数列:

\(1\).给定 \(n\) 个 \(0\) 和 \(n\) 个 \(1\),排成的长度为 \(2n\) 的序列,满足任意前缀序列中 \(0\) 的个数都不少于 \(1\) 的个数的序列有多少个。(将 \(01\) 序列置于坐标系中,起点定于原点。若 \(0\) 表示向右走,\(1\) 表示向上走,路径上的任意一点,横坐标大于等于纵坐标的合法路径数量。)

image

\(2\).一个栈的进栈序列为 \(1,2,3, \cdots ,n\) 有多少个不同的出栈序列?

将进栈视为 \(0\) ,将出栈视为 \(1\) ,则与第一个问题相同,故答案为 \(Cat_n\)

\(3\).\(n\) 个节点,可以构造多少不同二叉树?

设 \(f(n)\) 为 \(n\) 个节点构成的不同二叉树的数量。
该问题答案为 不同情况下左子树的方案数 \(\times\) 对应右子树的方案数 的和。
即 \(f(n)=f(0)\times f(n-1)+f(1)\times f(n-2)+\cdots+f(n-1)\times f(0)\)
会发现 \(f(n)=\sum_{i=1}^nf(i-1)\times f(n-i)\) ,与 \(Cat_n\) 的递推式相同,故答案为 \(Cat_n\) 。

 

卢卡斯定理/Lucas 定理:

Lucas 定理用于求解大组合数取模的问题,其中模数必须为素数。正常的组合数运算可以通过递推公式求解,但当问题规模很大,而模数是一个不大的质数的时候,就不能简单地通过递推求解来得到答案,需要用到 Lucas 定理。

对于质数 \(p\) 有:

\[\binom{n}{m}\bmod p=\binom{\lfloor n/p\rfloor}{\lfloor m/p\rfloor}\times \binom{n\bmod p}{m\bmod p} \bmod p \]

对于第二部分可以直接求组合数,对于第一部分可以继续递归 \(Lucas\) 。
单次时间复杂度 \(O(p\log p)\) ,可以通过预处理达到 \(O(p+\log p)\) 。

代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;

inline ll read(){
	ll s=0,k=1;
	char c=getchar();
	while(c>'9'||c<'0'){
		if(c=='-')k=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9'){
		s=(s<<3)+(s<<1)+(c^48);
		c=getchar();
	}
	return s*k;
}

const int N=1e5+10;
ll T,n,m,p,a[N],b[N];

ll ksm(ll a,ll b){
	ll t=1;
	for(;b;b>>=1,a=a*a%p)
		if(b&1) t=t*a;
	return t;
}

ll init(ll n,ll p){
	a[0]=1;
	for(int i=1;i<=n;i++) a[i]=(a[i-1]*i)%p;
	b[n]=ksm(a[n],p-2);
	for(int i=n-1;i>=0;i--){
		b[i]=b[i+1]*(i+1)%p;
	}
}

ll C(ll n,ll m,ll p){
	if(m>n) return 0;
	return a[n]*b[m]%p*b[n-m]%p;
}

ll Lucas(ll n,ll m,ll p){
	if(!m) return 1;
	return (C(n%p,m%p,p)*Lucas(n/p,m/p,p))%p;
}

int main(){
	T=read();
	while(T--){
		n=read();m=read();p=read();
		init(p-1,p);
		printf("%lld\n",Lucas(n+m,m,p)%p);
	}
	return 0;
}

 

exLucas:

求 \(C_n^m\bmod d\) ( \(d\) 不一定为质数)

1.

将 \(d\) 质因数分解为 \(d=p_1^{c_1}\times p_2^{c_2}\times\cdots\times p_k^{c_k}\)

\(\forall i,j\in[1,k]\) , \(p_i^{c_i}\) 与 \(p_j^{c_j}\) 互质,所以可以构造出如下同余方程:

\[\begin{cases}a_1\equiv C_n^m\ (\bmod \ p_1^{c_1})\\a_2\equiv C_n^m\ (\bmod \ p_2^{c_2})\\\cdots\\a^k\equiv C_n^m\ (\bmod \ p_k^{c_k})\end{cases} \]

在求出所有的 \(a_i\) 后,就可以利用中国剩余定理求解出 \(C_n^m\) 。

所以问题转化为求出 \(C_n^m\bmod p^\alpha\) 的值。

2.

但是同余方程 \(ax\equiv 1(\bmod \ p)\) (乘法逆元) 有解的充要条件为 \(\gcd(a,p)=1\) 。

然而题目显然无法保证有解,所以无法直接求 \({\rm inv}_{m!}\) 和 \({\rm inv}_{(n-m)!}\)

所以可将原式转化为:

\[\frac{\frac{n!}{p^x}}{\frac{m!}{p^y}\times \frac{(n-m)!}{p^z}}\times p^{x-y-z}\bmod p^\alpha \]

其中 \(x\) 为 \(n!\) 中包含 \(p\) 的个数,\(y,z\) 同理。

3.

若对于一个 \(n\) 可以求出 \(\frac{n!}{p^x}\) ,那么就可以求逆元了,所以现在要求 \(\frac{n!}{p^x}\bmod p^k\) 。

对 \(n!\) 进行变形可得:

\[\begin{array}{l}n!=1\times 2\times \cdots\times n\\ =(p\times 2p\times \cdots)\times\prod_{i=1,i\not\equiv 0(\bmod p)}^n i\\=p^{\lfloor\frac{n}{p}\rfloor}\times (\lfloor\frac{n}{p}\rfloor)!\times\prod_{i=1,i\not\equiv 0(\bmod p)}^n i\\=p^{\lfloor\frac{n}{p}\rfloor}\times (\lfloor\frac{n}{p}\rfloor)!\times(\prod_{i=1,i\not\equiv 0(\bmod p)}^{p^k} i)^{\lfloor\frac{n}{p^k}\rfloor}\times(\prod_{i=p^k\lfloor\frac{n}{p^k}\rfloor,i\not\equiv 0(\bmod p)}^ni)\end{array} \]

设 \(f(n)=\frac{n!}{p^x}\ (f(0)=1)\)

\[\therefore f(n)=f(\lfloor\frac{n}{p}\rfloor)\times(\prod_{i=1,i\not\equiv 0(\bmod p)}^{p^k} i)^{\lfloor\frac{n}{p^k}\rfloor}\times(\prod_{i=p^k\lfloor\frac{n}{p^k}\rfloor,i\not\equiv 0(\bmod p)}^ni)\\ \]

\[\therefore 原式=\frac{f(n)}{f(m)f(n-m)}p^{x-y-z}\bmod p^k \]

这样就可以用 \(exgcd\) 求逆元了。

最后利用中国剩余定理得到答案。单次时间复杂度 \(O(p\log p)\)

代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;

inline ll read(){
	ll s=0,k=1;
	char c=getchar();
	while(c>'9'||c<'0'){
		if(c=='-')k=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9'){
		s=(s<<3)+(s<<1)+(c^48);
		c=getchar();
	}
	return s*k;
}

ll n,m,p; 

ll ksm(ll a,ll b,ll mod){
	ll t=1;
	for(;b;b>>=1,a=a*a%mod)
		if(b&1) t=t*a%mod;
	return t;
}

void exgcd(ll a,ll b,ll &x,ll &y){
	if(!b){
		x=1,y=0;
		return ;
	}
	exgcd(b,a%b,x,y);
	ll t=x;
	x=y;
	y=t-a/b*y;
}

ll inv(ll n,ll mod){
	ll x,y;
	exgcd(n,mod,x,y);
	return (x%mod+mod)%mod; 
}

ll CRT(int n,ll a[],ll P[]){
	ll ans=0;
	for(int i=1;i<=n;i++) ans=(ans+a[i]*(p/P[i])%p*inv(p/P[i],P[i])%p)%p;
	return ans;
}

ll f(ll n,ll pi,ll pk){
	if(!n) return 1;
	ll t=1;
	for(ll i=2;i<=pk;i++) if(i%pi) (t*=i)%=pk;
	t=ksm(t,n/pk,pk);
	for(ll i=n/pk*pk+1;i<=n;i++) if(i%pi) (t*=(i%pk))%=pk;
	return t*f(n/pi,pi,pk)%pk;
}

ll C(ll n,ll m,ll pi,ll pk){
	int k=0;
	for(ll i=n;i;i/=pi) k+=i/pi;
	for(ll i=m;i;i/=pi) k-=i/pi;
	for(ll i=n-m;i;i/=pi) k-=i/pi;
	return f(n,pi,pk)*inv(f(m,pi,pk),pk)%pk*inv(f(n-m,pi,pk),pk)%pk*ksm(pi,k,pk)%pk;
}

ll exLucas(ll n,ll m,ll p){
	int cnt=0;
	ll a[20],P[20],tmp=p;
	for(int i=2;i*i<=p;i++)
		if(tmp%i==0){
			P[++cnt]=1;
			while(tmp%i==0) P[cnt]*=i,tmp/=i;
			a[cnt]=C(n,m,i,P[cnt]);
		} 
	if(tmp>1) P[++cnt]=tmp,a[cnt]=C(n,m,tmp,tmp);
	return CRT(cnt,a,P);
}

int main(){
	n=read();m=read();p=read();
	printf("%lld\n",exLucas(n,m,p));
	return 0;
}

 

莫比乌斯函数:

设正整数 \(N\) 分解质因数为 \(N=p_1^{c_1}\times p_2^{c_2}\times \cdots\times p_m^{c_m}\) ,
定义函数

\[\mu(N)=\begin{cases} 0 \ \ \ \exists i\in[1,m],c_i>1\\ 1 \ \ \ m\equiv 0\ (\bmod 2),\forall i\in[1,m],c_i=1\\ -1\ m\equiv 1\ (\bmod 2),\forall i\in[1,m],c_i=1 \end{cases} \]

注意莫比乌斯函数是积性函数

若只求一项莫比乌斯函数,分解质因数即可。

代码
int mobius(){
	for(int i=2;i<=n;i++){
		while(n%i==0){
			c[++cnt]=i;
			if(c[cnt]==c[cnt-1]) return 0;
			n/=i;
		}
	}
	if(n>1) cnt++;
	if(cnt&1) return -1;
	else return 1;
}

若求 \(1\sim N\) 的每一项数值,可以筛法计算。

埃氏筛
void mobius(){
	for(int i=1;i<=n;i++) miu[i]=1,v[i]=0;
	for(int i=2;i<=n;i++){
		if(v[i]) continue;
		miu[i]=-1;
		for(int j=2*i;j<=n;j+=i){
			v[j]=1;
			if((j/i)%i==0) miu[j]=0;
			else miu[j]*=i;
		}
	}
}
线性筛
void mobius(){
	ipr[1]=miu[1]=1;
	for(int i=2;i<=n;i++){
		if(!ipr[i]) pri[++cnt]=i,miu[i]=-1;
		for(int j=1;j<=cnt&&i*pr[j]<=n;j++){
			ipr[i*pr[j]]=1;
			if(i%pr[j]==0){
				miu[i*pr[j]]=0;
				break;
			}
			miu[i*pr[j]]=-miu[i];
		}
	}
}

 

[POI2007]ZAP-Queries

给出 \(a,b,d\),求满足 \(1 \leq x \leq a\),\(1 \leq y \leq b\),且 \(\gcd(x,y)=d\) 的二元组 \((x,y)\) 的数量。

题目即求:

\[\sum_{i=1}^a\sum_{j=1}^b [\gcd(i,j)==k]\\ =\sum_{i=1}^{\lfloor\frac{a}{k}\rfloor}\sum_{i=1}^{\lfloor\frac{b}{k}\rfloor} [\gcd(i,j)==1] \]

设 \(D[i,j,k]\) 表示满足 \(1\leq x\leq a,1\leq y\leq b\) 且 \(k\mid \gcd(x,y)\) 的二元组的方案数。显然为 \(k\mid a\ \&\& \ k\mid b\) 的方案数。\(1\sim a\) 中 \(k\) 的倍数有 \(\lfloor\frac{a}{k}\rfloor\) 个,所以 \(D[a,b,k]=\lfloor\frac{a}{k}\rfloor\times\lfloor\frac{b}{k}\rfloor\)
设 \(F[a,b]\) 表示满足 \(1\leq x\leq a,1\leq y\leq b\) 且 \(\gcd(x,y)=1\) 的方案数,会发现 \(D[a,b,i]\) 的系数恰好就是 \(\mu(i)\) ,即:

\[F[a,b]=\sum_{i=1}^{\min(a,b)} \mu(i)\times D[a,b,i] \]

然后发现 \(\forall i\in[x,\min(\lfloor\frac{a}{\lfloor\frac{a}{x}\rfloor}\rfloor,\lfloor\frac{b}{\lfloor\frac{b}{x}\rfloor}\rfloor)],D[a,b,i]\) 都相等,就可以用整除分块维护答案,共有 \(O(\sqrt{a}+\sqrt{b})\) 个段。并预处理 \(\mu(i)\) 的前缀和,即可快速求得答案。

第二类斯特林数:

定义:将 \(n\) 个不同的物品分成 \(m\) 个互不区分的非空组的方案数,记作 \(n \brace m\) 或 \(S(n,m)\)。
通式:\({n\brace {m}}=\sum_{i=0}^{m}{(-1)^{m-i}\cdot C^{i}_{m}\cdot i^n}\)
递推式:\(S(n,m)=S(n-1,m-1)+S(n-1,m)\times m\)

by ysx

标签:frac,组合,int,sum,times,vmatrix,数学,ll
From: https://www.cnblogs.com/classblog/p/18326138

相关文章

  • 数学建模竞赛中应掌握的10类算法
    数学建模竞赛中应掌握的10类算法这篇文章是搬运自2004年发表在MATHEMATICALMODELING即《数模》上的一篇文章,作者是董乘宇,曾任SHUMO.COM论坛“编程交流”版版主,获2002年全国大学生数学建模竞赛一等奖。尽管这篇文章至今已经有了整整20年的时间,但考虑到在数学建模领域或者......
  • 使用 useRequestURL 组合函数访问请求URL
    title:使用useRequestURL组合函数访问请求URLdate:2024/7/26updated:2024/7/26author:cmdragonexcerpt:摘要:本文介绍了Nuxt3中的useRequestURL组合函数,用于在服务器端和客户端环境中获取当前页面的URL信息。通过示例展示了如何在页面中使用此函数获取并显示URL及其......
  • mysql的组合索引
    创建联合索引修改表-创建索引创建表-创建索引创建结果:Seq_in_index:表示该列在索引中的位置,如果索引是单列的,则该列的值为1;如果索引是组合索引,则该列的值为每列在索引定义中的顺序。这里decie_id在组合索引中的顺序是1,question_id在组合索引中的顺序是2列在组合索引的......
  • Python - 检测字母模式而不迭代所有可能的组合
    对于可能不太有用的标题,我表示歉意,我不知道如何将这个问题总结为一句话。我正在尝试计算Python3.10中一个单词有多少个“单位”长。一个“单位”是(C表示辅音,V表示元音)CV或VC或C或V(后两者仅在没有配对时使用)可以制作)。例如,“件”将为三个单位......
  • 组合数学
    组合数学基本概念\(a^{\underlinem}\)表示\(a\)的\(m\)次下降幂。\(a^{\overlinem}\)表示\(a\)的\(m\)次上升幂。递推式\[{n\choosem}={n-1\choosem}+{n-1\choosem-1}\\\]证明:从组合意义上推导,在n个人中选m个相当于单独考虑最后一人,若他要选,则为......
  • 代码随想录算法训练营第 23 天 |LeetCode 39. 组合总和 LeetCode 40.组合总和II LeetC
    代码随想录算法训练营Day23代码随想录算法训练营第23天|LeetCode39.组合总和LeetCode40.组合总和IILeetCode131.分割回文串目录代码随想录算法训练营前言LeetCode39.组合总和LeetCode40.组合总和IILeetCode131.分割回文串一、基础1、回溯可以看成N叉树2、去......
  • 代码随想录算法训练营第 22 天 |LeetCode77. 组合 LeetCode 216.组合总和III LeetCode
    代码随想录算法训练营Day22代码随想录算法训练营第22天|LeetCode77.组合LeetCode216.组合总和IIILeetCode17.电话号码的字母组合目录代码随想录算法训练营前言LeetCode77.组合LeetCode216.组合总和IIILeetCode17.电话号码的字母组合一、基础1、回溯可以解......
  • 条件组合组件--vue3版
    参考手把手教你写一个条件组合组件此随笔是借鉴以上写的vue版,记录一下组件前期准备1.vue3的全套工具2.element-plus3.lodash数据结构主要是嵌套结构关键点在RelationGroup组件内引用本身,注意key值,不能用i,不然删除操作,会从最后删起组件结构主要是这3个文件引用......
  • 设计模式总结:适配器、桥接、组合和迭代器模式
    在之前的对话中,我们讨论了五种常见的Java设计模式:单例、工厂、策略、装饰器和观察者模式。现在,让我们继续探索其他四种设计模式:适配器、桥接、组合和迭代器模式。适配器模式概念:适配器模式是一种结构型设计模式,用于将一个类的接口转换为另一个类期望的接口。适配器模式......
  • 小学北师大数学目录
    小学数学北师大版一年级上册目录可爱的校园一生活中的数快乐的家园玩具小猫钓鱼文具快乐的午餐动物乐园二比较过生日下课啦跷跷板三加与减(一)一共有多少还剩下多少可爱的小猫猜数游戏背土豆跳绳可爱的企鹅......