基本概念

基本计数原理:

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

简单容斥与摩根定理:

  • \(\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;
}

 

 

例题:

数字求和:

\(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)!}) \]

posted @ 2024-07-02 19:27  programmingysx  阅读(3)  评论(0编辑  收藏  举报