20251013 排列组合 容斥总结
排列组合
排列:意思是从n个数里面选取m个数出来进行排列,总方案数就是AnmA_n^mAnm,代表n×(n−1)×……×(n−m+1)n \times(n-1) \times…… \times(n-m+1)n×(n−1)×……×(n−m+1)。例如,小明班有101010名同学,要选出333名去拍合照,总方案数是A103A_{10}^3A103,也就是10×9×8=72010 \times9 \times8=72010×9×8=720种方案。
组合:意思是从n个数里面选取m个数出来,总方案数是Cnm=Anmn!C_n^m = \frac{A_n^m}{n!}Cnm=n!Anm,也是n×(n−1)×⋯×(n−m+1)n×(n−1)×⋯×1\frac{n\times(n-1)\times\dots \times(n-m+1)}{n\times(n-1)\times\dots \times1}n×(n−1)×⋯×1n×(n−1)×⋯×(n−m+1)。例如,还是小明班有10名同学,但这次要选3名去搞卫生,那么总方案数应是,也就是10×9×8÷3÷2÷1=12010 \times9 \times8\div3\div2\div1 =12010×9×8÷3÷2÷1=120种方案。
组合数的规律:组合数具有对称性,也就是Cnm=Cnn−mC_n^m=C_n^{n-m}Cnm=Cnn−m,可以把它理解成从nnn个数中选mmm个数和从nnn个数中不选n−mn-mn−m个数,方案总数是相同的。
容斥原理
一道容斥原理练习题:某校六(1)班45名学生在暑假期间都参加了体育训练。具体数据如下:
- 参加足球队25人
- 参加排球队22人
- 参加游泳队24人
- 同时参加足球和排球12人
- 同时参加足球和游泳9人
- 同时参加排球和游泳8人
求:三项运动都参加的学生人数?
解题思路:
设三项都参加的人数为x。根据容斥原理公式:
总人数=足球+排球+游泳-足球∩排球-足球∩游泳-排球∩游泳+足球∩排球∩游泳
即45=25+22+24-12-9-8+x
解得x=3
因此,三项运动都参加的学生共3人。
例题
A - 盒子与球
动态规划,dp[i][j]dp[i][j]dp[i][j]表示i个球,jjj个盒子时的方案总数,那么初始值就是dp[0][0]=1dp[0][0]=1dp[0][0]=1,转移状态时可以从两个方面考虑,一是新加一个盒子装这个球,二是用目前的jjj个盒子装这个球,那么dp[i][j]=dp[i−1][j−1]+dp[i−1][j]∗jdp[i][j]=dp[i-1][j-1]+dp[i-1][j]*jdp[i][j]=dp[i−1][j−1]+dp[i−1][j]∗j。
#include<bits/stdc++.h>
using namespace std;
int dp[1005][1005];
int main(){int n,r;cin>>n>>r;dp[0][0]=1;//把0个球放到0个盒子里有一种方法,就是啥也不干for(int i=1;i<=n;i++){for(int j=1;j<=r;j++){dp[i][j]=dp[i-1][j-1]+dp[i-1][j]*j;}}for(int i=r;i>=2;i--){//不用乘1dp[n][r]*=i;//乘上r的阶乘,因为每个球不一样}cout<<dp[n][r];return 0;
}
C - Having Been a Treasurer in the Past, I Help Goblins Deceive
我们可以发现当一个字符串重新排序成下划线全部到中间减号一边一半时,该字符串所表示的数字最大,所以首先统计每一个字符的数量,最后按最优方案输出。
#include<bits/stdc++.h>
using namespace std;
int main(){int t;cin>>t;while(t--){int n;cin>>n;string s;cin>>s;s=" "+s;//好习惯,要养成long long x1=0,x2=0;for(int i=1;i<=n;i++){if(s[i]=='-')x1++;//如果是减号if(s[i]=='_')x2++;//如果是下划线}if(x1%2==0){//如果有偶数个减号cout<<x2*(x1/2)*(x1/2)<<endl;//直接平均分}else{cout<<x2*(x1/2+1)*(x1/2)<<endl;//不然有一边多一个}}return 0;
}
D - Replace Character
将这题献给未AC的dyc和xrc
容易发现,字母类型越少的字符串,不同的排列数量最少,那么题目既然要我们修改一个字符,我们肯定会挑数量只有一个的字符,这样可以减少字符类型,岂不妙哉!如果没有的话,就挑数量最少的字符,也可以。
但是要替换成哪一个字符呢?根据我们严谨的打表证明,替换成出现次数最多的字符最优。说了这么多废话,最终我们可以得出核心的写法,将出现次数最少的字符换成出现次数最多的字符就行了。
#include<bits/stdc++.h>
using namespace std;
//a=97,z=122
int a[125];//桶数组
int main(){ int t;cin>>t;while(t--){memset(a,0,sizeof a);//多行不清空,爆零两行泪int n;string s;cin>>n>>s;for(int i=0;i<n;i++){a[int(s[i])]++;//统计字符,不强转 int也可以}int mn=n,mx=0;//记录出现次数最多的数量和出现次数最少的数量(不含0)char x,y;//分别对应的字符for(int i=97;i<=122;i++){if(a[i]!=0){//如果出现过if(a[i]<mn){//更新mn=a[i];x=i;}if(a[i]>=mx){//更新mx=a[i];y=i;} }}for(int i=0;i<n;i++){if(s[i]==x){//找到x就替换成ys[i]=y;break;}}cout<<s<<endl;}return 0;
}