1.二项式定理
例题:计算系数
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
#include<cmath>
using namespace std;
typedef long long ll;
const ll mo=10007;
int a,b,k,n,m;
ll c[1007][1007];
ll qpow(int a,int b)
{
ll ans=1,base=a;
while(b)
{
if(b&1)
ans=ans*base%mo;
base=base*base%mo;
b>>=1;
}
return ans;
}
int main()
{
scanf("%d%d%d%d%d",&a,&b,&k,&n,&m);
c[0][0]=1;
for(int i=1;i<=k;i++)
c[i][0]=c[i][i]=1;
for(int i=1;i<=k;i++)
{
for(int j=1;j<i;j++)
{
c[i][j]=(c[i-1][j-1]+c[i-1][j])%mo;
}
}
ll ans=c[k][n]*qpow(a,n)%mo*qpow(b,m)%mo;
printf("%lld
",ans);
return 0;
}
2.扩展欧几里得
(ax+by=c)有解当且仅当(d=gcd(a,b)),且(d|c)。方程的通解为(x=frac{c}{d}x_{0}+kfrac{b}{d}),(y=frac{c}{d}y_{0}-kfrac{a}{d}),((kepsilon mathbb{Z})).
其中(x_{0})和(y_{0})的求解方法就是扩展欧几里得。
ll exgcd(ll a,ll b,ll &x,ll &y)
{
if(b==0)
{
x=1,y=0;
return a;
}
ll d=exgcd(b,a%b,x,y);
ll tmp=x;
x=y;
y=tmp-y*(a/b);
return d;//d为gcd
}
例题:同余方程
满分作法:原式可以转化为(ax+by=1),因为保证有解,只能(gcd(a,b)=1).找到第一个正整数解,只需先求解,在加即可。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
#include<cmath>
using namespace std;
typedef long long ll;
ll a,b;
ll xx,yy;
ll exgcd(ll a,ll b,ll &x,ll &y)
{
if(b==0)
{
x=1,
y=0;
return a;
}
ll d=exgcd(b,a%b,x,y);
ll tmp=x;
x=y;
y=tmp-y*(a/b);
return d;
}
int main()
{
scanf("%lld%lld",&a,&b);//由题gcd(a,b)=1,否则无解
exgcd(a,b,xx,yy);
while(xx<=0)
xx+=b;
printf("%lld
",xx);
return 0;
}
3.乘法逆元(也就是在取模意义下的除数)
1.方法一:扩展欧几里得求逆元(适用所有)
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
#include<cmath>
using namespace std;
typedef long long ll;
ll a,p,xx,yy;
void exgcd(ll a,ll b,ll &x,ll &y)
{
if(b==0)
{
x=1;
y=0;
return;
}
exgcd(b,a%b,x,y);
ll tmp=x;
x=y;
y=tmp-y*(a/b);
}
int main()
{
scanf("%lld%lld",&a,&p);
exgcd(a,p,xx,yy);
xx=(xx%p+p)%p;//有可能是负数,所以要这样取模
printf("%lld
",xx);
return 0;
}
2.快速幂(两数互质)
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
#include<cmath>
using namespace std;
typedef long long ll;
ll a,p;
ll qpow(ll a,ll b)
{
ll ans=1,base=a;
while(b){
if(b&1)
ans*=base,base%=p;
base*=base,base%=p;
b>>=1;
}
return ans;
}
int main()
{
scanf("%lld%lld",&a,&p);
printf("%lld
",qpow(a,p-2)%p);
return 0;
}
3.求连续数(1,2,3,4,5.....)的逆元
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
#include<cmath>
using namespace std;
typedef long long ll;
const int maxm=3e6+7;
ll p;
int n;
ll inv[maxm];
int main()
{
scanf("%d%lld",&n,&p);
inv[1]=1;
printf("1
");
for(int i=2;i<=n;i++)
{
inv[i]=(p-p/i)*inv[p%i]%p;//-p/i是负数所以+p
printf("%lld
",inv[i]);
}
return 0;
}
4.一个序列的逆元.
例题:乘法逆元2
我们设数组(a)的各项是:
(a_1,a_2,a_3......a_n)
则其逆元可表示为:
(frac{1}{a_1},frac{1}{a_2},frac{1}{a_3},......frac{1}{a_n})
我们取一个数列(s),表示:
(s_i = a_1 * a_2 * ...... * a_i)
其递推式为:
(s_i = s_{i-1}*a_i)
则:
(frac{1}{s_i} = frac{1}{a_1 * a_2 * ...... * a_i})
易得其递推式为:
(frac{1}{s_{i-1}} = frac{1}{s_{i}}*a_i)
于是(frac{1}{a_i})可以表示为
(frac{1}{a_i}=s_{i-1}*frac{1}{s_{i}})
其中(a_i)是已知的,(s_i)是线性递推的,而求出(frac{1}{s_n})后,可以再次线性递推出(frac{1}{s_i})。最后求出(frac{1}{a_i})。
最后用秦九韶优化即可。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
#include<cmath>
using namespace std;
typedef long long ll;
const int maxm=5e6+7;
int n;
inline int read()
{
char ch=getchar();int x=0,f=1;
while(ch<'0' || ch>'9') {
if(ch=='-') f=-1;
ch=getchar();
}
while(ch<='9' && ch>='0') {
x=x*10+ch-'0';
ch=getchar();
}
return x*f;
}
inline ll readl() {
char ch = getchar();
ll x = 0, f = 1;
while (ch < '0' || ch > '9') {
if (ch == '-')
f = -1;
ch = getchar();
}
while (ch <= '9' && ch >= '0') {
x = x * 10 + ch - '0';
ch = getchar();
}
return x * f;
}
ll p,k,a[maxm];
ll s[maxm];
ll inv[maxm];
ll ans=0;
ll qpow(ll a,ll b)
{
ll ans=1,base=a;
while(b){
if(b&1)
ans=ans*base,ans%=p;
base=base*base,base%=p;
b>>=1;
}
return ans;
}
int main()
{
n=read();
p=readl();
k=readl();
s[0]=1;
for(int i=1;i<=n;i++)
a[i]=readl();
for(int i=1;i<=n;i++)
s[i]=a[i]*s[i-1]%p;
inv[n]=qpow(s[n],p-2);
for(int i=n-1;i>=1;i--)
inv[i]=inv[i+1]*a[i+1]%p;
for(int i=n;i>=1;i--)
{
ll node=inv[i]*s[i-1]%p;
ans=((ans+node)%p*k%p);
}
printf("%lld
",ans);
return 0;
}
5.组合数取模(Lucas定理)
(Lucas_{m}^n equiv Lucas_{m/p}^{n/p} * C_{n mod p}^{m mod p} pmod p)
例题:卢卡斯定理
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
#include<cmath>
using namespace std;
typedef long long ll;
const int maxm=2e5+7;
ll n,m,p;
int t;
ll inv[maxm];
ll qpow(ll a,ll b)
{
ll ans=1,base=a;
while(b)
{
if(b&1) ans=base*ans%p;
base=base*base%p;
b>>=1;
}
return ans;
}
ll C(ll n,ll m)
{
if(n<m) return 0;
return inv[n]*qpow(inv[n-m],p-2)%p*qpow(inv[m],p-2)%p;
}
ll lucas(ll n,ll m)
{
if(n<m) return 0;
if(n==0) return 1;
return C(n%p,m%p)*lucas(n/p,m/p)%p;
}
int main()
{
scanf("%d",&t);
while(t--)
{
scanf("%lld%lld%lld",&n,&m,&p);
inv[0]=1;
for(int i=1;i<=n+m;i++)
inv[i]=inv[i-1]*i%p;
printf("%lld
",lucas(n+m,m));
}
return 0;
}
6.错排问题
递推公式:(d[i]=(i-1)(d[i-1]+d[i-2])),其中(d[0]=1),(d[1]=0);
证明:见此博客。
例题:排列计数
满分作法:相当于在(n)个数中选(m)个数,剩下的错排即可
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
#include<cmath>
using namespace std;
typedef long long ll;
const int maxm=1e6+7;
const ll mo=1e9+7;
int t;
int n,m;
ll d[maxm];//错排数
ll inv[maxm];
ll qpow(ll a,ll b)
{
ll ans=1,base=a;
while(b){
if(b&1)
ans=ans*base%mo;
base=base*base%mo;
b>>=1;
}
return ans;
}
ll C(ll n,ll m)
{
if(n<m) return 0;
return inv[n]*qpow(inv[n-m],mo-2)%mo*qpow(inv[m],mo-2)%mo;
}
int main()
{
scanf("%d",&t);
d[0]=1;//要特殊处理d[0]
d[1]=0;
d[2]=1;
for(int i=3;i<=maxm-7;i++)
d[i]=(i-1)*(d[i-1]+d[i-2])%mo;
inv[0]=1;
for(int i=1;i<=maxm-7;i++)
inv[i]=inv[i-1]*i%mo;
while(t--)
{
scanf("%d%d",&n,&m);
printf("%lld
",C(n,m)*d[n-m]%mo);
}
return 0;
}