885. 求组合数 I(递推)
给定 n 组询问,每组询问给定两个整数 a,b,请你输出 C a b m o d ( 1 0 9 + 7 ) C_a^b mod (10^9+7) Cabmod(109+7) 的值。
输入格式
第一行包含整数 n。
接下来 n 行,每行包含一组 a 和 b。
输出格式
共 n 行,每行输出一个询问的解。
数据范围
1
≤
n
≤
10000
1≤n≤10000
1≤n≤10000,
1
≤
b
≤
a
≤
2000
1≤b≤a≤2000
1≤b≤a≤2000
输入样例:
3
3 1
5 3
2 2
输出样例:
要考虑时间复杂度的限制,观察数据范围
10000个组合数,组合数上下标a,b范围是2000,利用公式
c
(
a
,
b
)
=
a
!
/
(
b
!
∗
(
a
−
b
)
!
)
c(a,b)=a!/(b!*(a-b)!)
c(a,b)=a!/(b!∗(a−b)!),每计算一个组合数时间复杂度就是a,b的范围2000逐一计算组合数,
10000
∗
2000
=
2
∗
1
0
8
10000*2000=2*10^8
10000∗2000=2∗108
相比于预处理,算出a,b任一上下标组合的组合数,复杂度不过
2000
∗
2000
=
4
∗
1
0
6
2000*2000=4*10^6
2000∗2000=4∗106
自然选后者
3
10
1
#include <iostream>
using namespace std;
const int N=2005;
const int mod=1e9+7;
int c[N][N];
void init(){//求出$C_i^j$
for(int i=0;i<N;i++){
for(int j=0;j<=i;j++){
if(j==0)c[i][j]=1;
else c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
}
}
}
int main(){
init();
int n;
cin>>n;
int a,b;
while(n--){
cin>>a>>b;
cout<<c[a][b]<<endl;
}
return 0;
}
886. 求组合数 Ⅱ(初始化fact[])
数据范围换为
1
≤
n
≤
10000
1≤n≤10000
1≤n≤10000,
1
≤
b
≤
a
≤
1
0
5
1≤b≤a≤10^5
1≤b≤a≤105
这时
n
∗
a
<
a
∗
a
n*a<a*a
n∗a<a∗a,就应该给一对a,b,求这个组合数了
而不再是先递推保存好
对于getC(int m,int n)
可以是
return fact[n]*quickpow(fact[n-m],mod-2)%mod*quickpow(fact[m],mod-2)%mod;
也可以先递推处infact[N]数组
return fact[n]*infact[n-m]%mod*infact[m]%mod;
只需要initfact初始化fact数组时同时初始化infact数组
void initfact(){
fact[0]=infact[0]=1;
for(int i=1;i<N;i++){
fact[i]=i*fact[i-1] %mod;
infact[i]=infact[i-1]*quickpow(i,mod-2)%mod;
}
}
1
x
!
\displaystyle \frac{1}{x!}
x!1%
m
o
d
mod
mod==
1
(
x
−
1
)
!
\displaystyle \frac{1}{(x-1)!}
(x−1)!1*
i
n
f
(
x
)
inf(x)
inf(x) %
m
o
d
mod
mod
除以x相当于乘上x的逆元
#include <iostream>
using namespace std;
typedef long long ll;
const int N=100005,mod=1e9+7;
ll fact[N];//fact[i]求i得阶乘 注意了N是组合数下标的范围,不是样例数
void initfact(){
fact[0]=1;
for(int i=1;i<N;i++){
fact[i]=i*fact[i-1] %mod;
}
}
ll quickpow(ll b,ll e){
b%=mod;
ll res=1;
while(e){
if(e&1)res=(b*res)%mod;
b=(b*b)%mod;
e>>=1;
}
return res;
}
ll getC(int n,int m){//求C_n^m
return fact[n]*quickpow(fact[n-m],mod-2)%mod*quickpow(fact[m],mod-2)%mod;
}//两个阶乘不会爆ll,这三个相乘会爆,一定要在两个乘完后%一下
int main(){
initfact();
int n;
cin>>n;
int a,b;
while(n--){
cin>>a>>b;
cout<<getC(a,b)<<endl;
}
return 0;
}
887. 求组合数Ⅲ(卢卡斯定理,模p在 1 0 5 10^5 105左右,n、m不爆longlong就行1≤n≤m≤ 10 ^ 18, 时间复杂度 O ( p ∗ l o g p m ∗ l o g 2 p O(p*log_pm*log_2p O(p∗logpm∗log2p)
给定 n 组询问,每组询问给定三个整数 a,b,p,其中 p 是质数,请你输出 C b a m o d p C_b^a\ mod\ p Cba mod p的值。
输入格式
第一行包含整数 n。
接下来 n 行,每行包含一组 a,b,p。
输出格式
共 n 行,每行输出一个询问的解。
数据范围
1
≤
n
≤
20
,
1
≤
b
≤
a
≤
1
0
1
8
,
1
≤
p
≤
1
0
5
,
1≤n≤20, \quad 1≤b≤a≤ 10 ^ 18, \quad 1≤p≤10^5,
1≤n≤20,1≤b≤a≤1018,1≤p≤105,
输入样例:
3
5 3 7
3 1 5
6 4 13
输出样例:
3
3
2
#include <iostream>
using namespace std;
typedef long long ll;
ll n,m,p;
void initfac(){
fac[0]=1;
for(int i=1;i<N;i++){
fac[i]=i*fac[i-1]%p;
}
}
ll quickpow(ll b,ll e){
b%=p;
ll res=1;
while(e){
if(e&1)res=res*b%p;
b=(b*b)%p;
e>>=1;
}
return res%p;
}
ll C(ll a,ll b){
if(b>a)return 0;
if(b>a-b)b=a-b;//C_a^b和C_a^(a-b)
ll x,y;//分别代表分子分母
x=y=1;//不初始化就等着乘0把
for(int i=0;i<b;i++){//C_5^2
x=x*(a-i)%p;
y=y*(i+1)%p;
}
return x*quickpow(y,p-2)%p;
}
ll lucas(ll a,ll b){
if(b==0)return 1;
return C(a%p,b%p)*lucas(a/p,b/p)%p;
}
int main(){
int t;
cin>>t;
while(t--){
cin>>n>>m>>p;
cout<<lucas(n,m)<<endl;
}
return 0;
}
卢卡斯定理模板
如果没有先给出mod,是不能预先初始化阶乘矩阵的,也不能递推(1e^5地平方必然超时)
必须这样实实在在地求
ll C(int a,int b){
if(b>a)return 0;
if(b>a-b)b=a-b;//C_a^b和C_a^(a-b)对求预处理fac无影响,这里有
ll x,y;//分别代表分子分母
x=y=1;//不初始化就等着乘0把
for(int i=0;i<b;i++){//C_5^2
x=x*(a-i)%p;
y=y*(i+1)%p;
}
return x*quickpow(y,p-2)%p;
}
#include <iostream>
using namespace std;
const int N=1e5+2;
typedef long long ll;
ll fac[N];
int n,m,p;
void initfac(){
fac[0]=1;
for(int i=1;i<N;i++){
fac[i]=i*fac[i-1]%p;
}
}
ll quickpow(ll b,ll e){
b%=p;
ll res=1;
while(e){
if(e&1)res=res*b%p;
b=(b*b)%p;
e>>=1;
}
return res%p;
}
ll getC(int a,int b){
if(b>a)return 0;
// if(b>a-b)b=a-b;//其实算C_a^b和C_a^(a-b)一样的啦,没必要换
return fac[a]*quickpow(fac[b],p-2)%p*quickpow(fac[a-b],p-2)%p;
}
ll C(int a,int b){
if(b>a)return 0;
if(b>a-b)b=a-b;//C_a^b和C_a^(a-b)
ll x,y;//分别代表分子分母
x=y=1;//不初始化就等着乘0把
for(int i=0;i<b;i++){//C_5^2
x=x*(a-i)%p;
y=y*(i+1)%p;
}
return x*quickpow(y,p-2)%p;
}
ll lucas(int a,int b){
if(b==0)return 1;
return C(a%p,b%p)*lucas(a/p,b/p)%p;
}
int main(){
int t;
cin>>t;
while(t--){
cin>>n>>m>>p;
n+=m;
cout<<lucas(n,m)<<endl;
}
return 0;
}
888. 求组合数 Ⅳ(结果不取模,质因数的幂次高精度相乘,一个(阶乘)数总能分解成若干个质因数的幂次的乘积,求某个质因数的幂次几何)
输入 a,b,求 C b a C_b^a Cba 的值。
注意结果可能很大,需要使用高精度计算。
输入格式
共一行,包含两个整数 a 和 b。
输出格式
共一行,输出
C
b
a
C_b^a
Cba 的值。
数据范围
1
≤
b
≤
a
≤
5000
1≤b≤a≤5000
1≤b≤a≤5000
输入样例:
5 3
输出样例:
10
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
typedef long long ll;
const int N=5005;
int vis[N];
int prime[N];
int cnt=0;
void getPrime(int n){//欧拉筛法
vis[0]=vis[1]=1;
for(int i=2;i<=n;i++){
if(!vis[i])prime[cnt++]=i;
for(int j=0;j<cnt&&prime[j]<=n/i;j++){//i*prime[j]<n,在右边除以i不会溢出
vis[i*prime[j]]=1;
if(i%prime[j]==0)break;
}
}
}
int getExpon(int n,int p){ //
//n!分解成质因数的幂次相乘的形式,其中质因数的幂次
//求一个数的阶乘 这个数 分解成质因数的幂次相乘的形式
//对于质因子p的幂次是多少,阶乘是若干个数相乘,只要把其中
//是p的倍数的数 、是p^2的倍数的数、是p^3的倍数的数…
//把这些倍数数值相加,不会重复的,虽然 是p^3的倍数的数也是是p^2的倍数
//在 是p^3的倍数的数这儿幂次要算上3,但在p、p^2那儿各算了一次
//在 是p^x的倍数的数这儿幂次要算上x,但在p、p^2…p^(x-1)那儿
//各算一次,共算了x-1次
int res=0;
while(n){
res+=n/p;//是p的倍数的数
n/=p;//1~n/p 有x个是p的倍数的数 ,则1~n有x个是p^2的倍数的数
}
return res;
}
int e[N];//e[i]记录getPrime[i]这个质因数的幂次
vector<int> mul(vector<int> a,int b){//a数组表示的成熟就是倒置的
vector<int> c;
int temp=0;
for(int i=0;i<a.size();i++){
temp+=a[i]*b;
c.push_back(temp%10);
temp/=10;
}
while(temp){//处理剩余0
c.push_back(temp%10);
temp/=10;
}
while(c.size()>1&&c.back()==0)c.pop_back();//去先导0
return c;
}
int main(){
int n,m;
cin>>n>>m;
getPrime(n);
for(int i=0;i<cnt;i++){
int p=prime[i];
e[i]=getExpon(n,p)-getExpon(m,p)-getExpon(n-m,p);
}
//求Prime[i] 的e[i]次方
vector<int> res;
res.push_back(1);
for(int i=0;i<cnt;i++){//低精度连乘出高精度(高精度*低精度)
for(int j=0;j<e[i];j++){//Prime[i] 连乘e[i]次
res=mul(res,prime[i]);
}
}
reverse(res.begin(),res.end());
for(int i=0;i<res.size();i++){
cout<<res[i];
}
return 0;
}
889. 满足条件的01序列
给定 n 个 0 和 n 个 1,它们将按照某种顺序排成长度为 2n 的序列,求它们能排列成的所有序列中,能够满足任意前缀序列中 0 的个数都不少于 1 的个数的序列有多少个。
输出的答案对 1 0 9 + 7 10^9+7 109+7 取模。
输入格式
共一行,包含整数 n。
输出格式
共一行,包含一个整数,表示答案。
数据范围
1
≤
n
≤
1
0
5
1≤n≤10^5
1≤n≤105
输入样例:
3
输出样例:
5
quickpow两个参数一定记得全部用longlong,否则会出现结果为0
全用longlong啦,一了百了
#include <iostream>
using namespace std;
typedef long long ll;
const int mod=1e9+7;
const int N=1e5+5;
ll quickpow(ll b,ll e){
b%=mod;
ll res=1;
while(e){
if(e&1)res=res*b%mod;
b=b*b%mod;
e>>=1;
}
return res;
}
ll C(int n,int m){
if(m>n)return 0;
if(n-m<m)m=n-m;
ll x,y;//分子分母
x=y=1;
for(int i=0;i<m;i++){
x=x*(n-i)%mod;
y=y*(i+1)%mod;
}
return x*quickpow(y,mod-2)%mod;
}
int main(){//C_2n^n-C_2n^(n-1)=C_2n^n/(n+1)
//C_2n^n=(2n)!/n!/n!
//C_5^2=5*4/2*1
int n;
cin>>n;
cout<<C(2*n,n)*quickpow(n+1,mod-2)%mod;
return 0;
}
伯努利错装信封问题
递归解决
f ( n ) = ( n − 1 ) ∗ ( f ( n − 1 ) + f ( n − 2 ) ) f(n)=(n-1)*(f(n-1)+f(n-2)) f(n)=(n−1)∗(f(n−1)+f(n−2))
错排:n封信放入n个信封,要求全部放错,共有多少种放法,记n个元素的错排总数为f(n)
假设有n封信,第一封信可放在(2-n)的任一个信封里,共n-1种放法,设第一封信放在了第k个信封里,若此时第k封信放在了第1个信封里,则只要将剩下的n-2错排,即f(n-2),若第k封信没有放在了第1个信封里,可将第1封信的位置看成是“第k个位置”,即将n-1封信错排,即为f(n-1)
从第一封信开始匹配,以信封数量为规模 有两种情况
1、假设 信1和信封x搭配 ,信x和信封1搭配 ,解决问题规模缩小到n-2
当然得注意,x只有n-1种选择
2、假设 信1和信封x搭配 ,信x不和信封1搭配 ,解决问题规模缩小到n-1
x还是有n-1种选择
0%错误,why?没用long long呀
#include <iostream>
using namespace std;
int fac(int n){
if(n==0||n==1)return 0;
if(n==2)return 1;
return (n-1)*(fac(n-1)+fac(n-2));
}
int main(){
int n;
while(cin>>n){
cout<<fac(n)<<endl;
}
return 0;
}
#include <iostream>
using namespace std;
typedef long long ll;
ll fac(int n){
if(n==0||n==1)return 0;
if(n==2)return 1;
else return (n-1)*(fac(n-1)+fac(n-2));
}
int main(){
int n;
while(cin>>n){
cout<<fac(n)<<endl;
}
return 0;
}
#include<bits/stdc++.h>
using namespace std;
const int N=22;
long long f[N];
int main()
{
f[1]=0,f[2]=1,f[3]=2;
for(int i=4;i<=20;i++)
f[i]=(i-1)*(f[i-2]+f[i-1]);
int n;
while(cin>>n) {
cout<<f[n]<<endl;
}
return 0;
}
全错位排列定理
#include <iostream>
using namespace std;
typedef long long ll;
ll fac[25];
void init(){
fac[1]=1;
for(int i=2;i<=20;i++){
fac[i]=i*fac[i-1];
}
}
int main(){
init();//不要忘记init呀
int n;
while(cin>>n){
ll s=fac[n];
ll sum=0;
for(int i=2;i<=n;i++){//n!(1-1/1! +1/2! -1/3!……
if(i%2==0)sum+=s/fac[i];
else sum-=s/fac[i];
}
cout<<sum<<endl;
}
return 0;
}