组合数的第一种枚方式: 求c[a][b]
c[a][b]: a * (a - 1) * (a - 2) * .... * (a - b + 1) / [ b * (b- 1) * (b - 2) * ... 1]
如果要用这个方式的话,需要注意,
a 从大到小枚举 , b 从小到大枚举.
因为a 从大到小枚举 和 b 从大到小枚举,可能会出现答案不准确的情况。
代码:
# include <iostream>
using namespace std;
int main()
{
int a,b;
scanf("%d %d",&a,&b);
int temp = 1;
for(int i = a , j = 1 ; j <= b ; i-- , j++ )
{
temp = temp * i / j;
}
printf("%d\n",temp);
return 0;
}
组合数的第二种方式:(a,b)小但是询问次数多时
时间复杂度:O(a * b)
原理: c[a][b] = c[a - 1][b] + c[a][b];
# include <iostream>
using namespace std;
const int N = 2010 , mod = 1e9 + 7;
int n;
int c[N][N];
int main()
{
for(int i = 0 ; i < N ; i++)
{
for(int j = 0 ; j <= i ; j++)
{
if(!j)
{
c[i][j] = 1;
}
else
{
c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]);
}
}
}
scanf("%d",&n);
while(n--)
{
int a,b;
scanf("%d %d",&a,&b);
printf("%d\n",c[a][b]);
}
return 0;
}
组合数的第三种方式:a,b大概在10^5左右时
时间复杂度:O(nlogn)
原理: c[a][b] = a ! / ( b ! * (a - b) !) , 所以我们预处理出所有阶乘和 阶乘的逆
# include <iostream>
using namespace std;
const int N = 1e5 + 10 , mod = 1e9 + 7;
int fact[N],infact[N];
int n;
int qmi(int a ,int b , int mod)
{
int res = 1;
while(b)
{
if(b & 1)
{
res = (long long)res * a % mod;
}
b >>= 1;
a = (long long)a * a % mod;
}
return res;
}
int main()
{
fact[0] = infact[0] = 1;
for(int i = 1 ; i < N ; i++)
{
fact[i] = (long long)fact[i - 1] * i % mod;
infact[i] = (long long)infact[i - 1] * qmi(i,mod -2 , mod) % mod;
}
scanf("%d",&n);
while(n--)
{
int a,b;
scanf("%d %d",&a,&b);
printf("%lld\n",(long long)fact[a] * infact[a - b] % mod * infact[b] % mod);
}
return 0;
}
组合数的第四种方式:a,b很大,但是 % mod的mod的值很小时
时间复杂度:p * log[p][a] * log[2][p]
原理:c[a][b] = c[a % mod][b % mod] * c[a / mod][b / mod]
# include <iostream>
using namespace std;
int qmi(int a , int b , int p)
{
int res = 1;
while(b)
{
if(b & 1)
{
res = (long long)res * a % p;
}
b >>= 1;
a = (long long)a * a % p;
}
return res;
}
int c(int a , int b , int p)
{
int res = 1;
for(int i = 1 , j = a ; i <= b ; i++,j--)
{
res = (long long)res * j % p;
res = (long long)res * qmi(i,p - 2 , p) % p;
}
return res;
}
int lucas(long long a ,long long b ,long long p)
{
if(a < p && b < p)
{
return c(a,b,p);
}
return (long long)c(a % p , b % p , p) * lucas(a / p , b / p , p) % p;
}
int main()
{
int n;
scanf("%d",&n);
while(n--)
{
long long a,b,p;
scanf("%lld %lld %lld",&a,&b,&p);
printf("%d\n",lucas(a,b,p));
}
return 0;
}
组合数的第五种方式:使用高精度求解
原理:还是使用 c[a][b] == a ! / b! / (a - b)!
方式:我们可以使用筛质数的方式,通过筛质数的方式筛出a!,b!,(a-b)!中的质数和对应的指数.
然后通过高精度的方式求解出来。
# include <iostream>
# include <vector>
using namespace std;
const int N = 5010;
int prim[N],cnt;
bool choose[N];
int sum[N];
void get_prim(int a) // 线性筛质数
{
for(int i = 2; i <= a ; i++)
{
if(!choose[i])
{
prim[++cnt] = i;
}
for(int j = 1 ; prim[j] <= a / i ; j++)
{
choose[i * prim[j]] = true;
if(i % prim[j] == 0)
{
break;
}
}
}
}
int get(int x , int p) // 求阶乘x! 关于 p 的指数
{
int res = 0;
while(x)
{
res += x / p;
x /= p;
}
return res;
}
vector<int> mul(vector<int> t , int p)
{
int d = 0;
vector<int> c;
for(int i = 0 ; i < t.size() || d; i++)
{
if(i < t.size())
{
d += t[i] * p;
}
c.push_back(d % 10);
d /= 10;
}
return c;
}
int main()
{
int a,b;
scanf("%d %d",&a,&b);
get_prim(a);
for(int i = 1 ; i <= cnt ; i++)
{
int p = prim[i];
sum[i] = get(a,p) - get((a - b),p) - get(b,p); // 对应的p的指数
}
vector<int> t;
t.push_back(1);
for(int i = 1 ; i <= cnt ; i++)
{
int p = prim[i];
for(int j = 1 ; j <= sum[i] ; j++)
{
t = mul(t,p);
}
}
for(int i = t.size() - 1; i >= 0 ; i--)
{
printf("%d",t[i]);
}
printf("\n");
return 0;
}