组合基本概念
基本计数原理:
分类计算加法原理,分布计算乘法原理。
简单容斥与摩根定理:
代码可以利用
或状压实现
摩根定理: 交集的补等于补集的并,并集的补等于补集的交。
即
组合计数:
排列数:
从
组合数:
从
性质:
(杨辉三角)
组合数求解:
单个组合数
代码
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;
}
求
代码
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];
}
}
二项式定理:
高精组合数:
求
所以可以对 阶乘进行质因数分解为 (方法在基础数论博客里)
然后对和 分别进行质因数分解,每次使 减一。
最后统计结果。
时间复杂度(高精另算)
代码
#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;
}
多重集排列数:
多重集组合数:
1.
设
个元素构成的集合是
且 (隔板法)
答案为
2.
若先不考虑
的限制,从 中取出 个元素,则方案数为
设表示包含至少 个 的多重集,即从 中先取出 个 , 然后再任选 个元素构成 ,可得不同的 的数量为
那么进一步思考,从中先取出 个 和 个 ,然后再任选 个元素,构成的集合即为 ,方案数为
根据容斥原理可得:故答案为
代码
#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;
}
例题:
数字求和:
设
答案对
考虑除去
这一个数字之外的其他数字中:
,都会在每一位上出现 次。
所以这些数字对答案的贡献就是
故答案为
圆盘染色:
考虑如果
的颜色与 相同,则将 看作一个整体。
那么块有 种选择,则方案数为 ,( 表示分成 块的方案数)
考虑如果的颜色与 不同,将 看作一个整体。
那么块有 种选择,则方案数为 。
()
数三角形:
给定一个
【三角形数量】等于【任选三个点的方案数】减去【三点共线的方案数】
【三点共线的方案数】等于【横着的】加【竖着的】加【斜着的】
【横着的】:
【竖着的】:
斜着的直线按斜率正负分为两种,并且这两种的方案数是相等的。因此,我们只需要计算出斜率为正的方案数,再乘以即可。将核心计算内容——斜率为正的方案数记为 。
假设网格中平行于底边 轴,长度为 , 平行于侧边 轴,长度为 ,那么 上的整点数量为 (不算 两点)。 此时还能继续化简:
至此可以
求得答案。
Counting swaps:
给定你一个
对于一个排列
,每一个 向 连一条无向边,构成一张由若干环组成的无向图,目标状态即为 个自环。 设
表示一个大小为 的环,在保证交换次数最少的情况下,有多少种方法将其变成目标状态。 每一次交换可以把大小为
的环拆成大小为, 的两个环( ) 。设 表示有多少种交换方法可以将一个大小为 的环拆成两个大小分别为 的环。可以发现:当 时, ,否则 。
环需要 次交换达到目标状态,同理 环需要 次操作,由于 环和 环的操作互不干扰且可以随意排列,因此得到转移方程: (在打表后可发现
)
假设排列中有个大小为 的环,则
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探