组合数学
一、加乘原理
加法原理
一件事,有
乘法原理
一件事,有
二、排列数与组合数
排列数
从
组合数
从
也可以记为:
读作:
三、组合数的性质
性质一
证明:从
性质二
证明:对于最后一个元素,如果不选它,那么在剩下的
这是组合数的递推公式。
性质三
证明:根据二项式定理:
显然,
性质四
证明:若
若
P3197 [HNOI2008] 越狱
运用“正难则反”思想,问题可以转化为“所有状态减去任意相邻犯人宗教都不同的状态”。
先考虑所有状态。每个人信仰的宗教有
再考虑任意相邻的犯人宗教不同的状态。只考虑第一个人,他信仰的宗教有
综上,答案就是
P2822 [NOIP2016 提高组] 组合数问题
利用性质二递推求得组合数,然后再对
P1350 车的放置
将原图分为三个矩形,分别是左上角,左下角,右下角的矩形。
然后先考虑对于
再考虑原题的答案。假设左下角的矩形放了
P3166 [CQOI2014] 数三角形
问题可以转化为:任选三个点的方案数
任选三点的方案数:
三点共线的方案数:
先考虑平行于
再考虑剩下的方案数。三点所在的直线斜率可正可负,但斜率为正的方案数与斜率为负的方案数相等,所以先只考虑斜率为正的方案数,再乘以
引理:对于两个点,如果它们的横坐标差为
而我们要求的是三点共线的方案数,相当于求固定两个点时,中间的第三个点的数量,所以应该是
乘以
时间复杂度:
但这还不够!
看到
时间复杂度
int work(int d){
return ((m/d)*(m+1)-d*((m/d)*(m/d+1)/2))*((n/d)*(n+1)-d*((n/d)*(n/d+1)/2));
}
signed main(){
pre_work();
scanf("%lld%lld",&n,&m);
for(int d=1;d<=min(n,m);++d)
ans=(ans+(phi[d]*work(d)));
ans-=(n*(n+1)/2)*(m*(m+1)/2);
ans=ans*2;
ans=(ans+((n+1)*c(m+1,3)+(m+1)*c(n+1,3)));
ans=(c((n+1)*(m+1),3)-ans);
printf("%lld\n",ans);
return 0;
}
四、容斥原理
设
P1450 硬币购物
问题可以转化为:选任意多的硬币购买的方案数
对于前半部分,只需要预处理一个完全背包即可。
对于后半部分,先状态压缩枚举哪些硬币用的次数超过限制,然后可以强制让它们选
while(n--){
scanf("%lld%lld%lld%lld%lld",&d[1],&d[2],&d[3],&d[4],&s);
ans=0;
for(int i=0;i<(1<<4);++i){
int tmp=i,num=1,cnt=0,sum=0;
while(tmp){
if(tmp&1) ++cnt,sum+=(1+d[num])*c[num];
tmp>>=1;++num;
}
if(s-sum>=0){
if(cnt&1) ans-=f[s-sum];
else ans+=f[s-sum];
}
}
printf("%lld\n",ans);
}
五、二项式反演
形式
形式
形式
其中形式1与形式2较为常用。形式1常用于“至多”与“恰好”的相互转化,形式2常用与“至少”与“恰好”的相互转化。
P6478 [NOI Online #2 提高组] 游戏
题目中要求的“恰好”,直接求较难,可以转化为“至少”。令
因此,只需要求出
求
如果只考虑子树,那么有状态转移方程:
用树形背包即可。
然后再考虑第
含义是,小
求出
void dfs(int x,int fa){//预处理出a[i]和b[i]
a[x]=(s[x-1]=='0');
b[x]=1-a[x];
for(int i=head[x];i;i=nxt[i])
if(ver[i]!=fa){
dfs(ver[i],x);
a[x]+=a[ver[i]];b[x]+=b[ver[i]];
}
}
void solve(int x,int fa){//DP部分
f[x][0]=1;siz[x]=0;//siz[x]不是子树大小,而是状态转移的上界
for(int i=head[x];i;i=nxt[i])
if(ver[i]!=fa){
solve(ver[i],x);
for(int j=0;j<=siz[x]+siz[ver[i]];++j)
g[j]=f[x][j],f[x][j]=0;
for(int j=0;j<=siz[x];++j)
for(int k=0;k<=siz[ver[i]];++k)
f[x][j+k]=(f[x][j+k]+(g[j]*f[ver[i]][k]%mod))%mod;
siz[x]+=siz[ver[i]];
}
int tmp=((s[x-1]=='0')?b[x]:a[x]);
for(int i=siz[x];i>=0;--i)
f[x][i+1]=(f[x][i+1]+(f[x][i]*max(0ll,tmp-i)%mod))%mod;
if(f[x][siz[x]+1])
++siz[x];
}
六、斯特林数
第一类斯特林数
将
递推公式:
其含义是:考虑最后一个元素,它可以放到前面的环中,也可以新开一个环
性质一
一个置换会有若干个环,而置换的总数是
性质二
证明可以考虑归纳法,然后结合递推公式。这个性质给出了同一行的第一类斯特林数的生成函数。
P5408 第一类斯特林数·行
由性质二可知,只需要求出
考虑倍增,加上已经求出了
P5395 第二类斯特林数·行
求
首先考虑把
上述问题可以看成“至多有
其中,
根据二项式反演的形式一,有:
也就是:
然后令
signed main(){
scanf("%lld",&n);
inv[0]=1;
for(int i=1;i<=n;++i)
inv[i]=inv[i-1]*fpow(i,mod-2)%mod;
int w=1;
for(int i=0;i<=n;++i){
a[i]=fpow(i,n)*inv[i]%mod;
b[i]=(w*inv[i]+mod)%mod;
w*=-1;
}
mul(a,b,n);//多项式乘法
for(int i=0;i<=n;++i)
printf("%lld ",a[i]);
printf("\n");
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】