数论
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\n",ans);
return 0;
}
2.扩展欧几里得
\(ax+by=c\)有解当且仅当\(d=gcd(a,b)\),且\(d|c\)。方程的通解为\(x=\frac{c}{d}x_{0}+k\frac{b}{d}\),\(y=\frac{c}{d}y_{0}-k\frac{a}{d}\),(\(k\epsilon \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\n",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\n",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\n",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\n");
for(int i=2;i<=n;i++)
{
inv[i]=(p-p/i)*inv[p%i]%p;//-p/i是负数所以+p
printf("%lld\n",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\n",ans);
return 0;
}
5.组合数取模(Lucas定理)
\(Lucas_{m}^n \equiv Lucas_{m/p}^{n/p} * C_{n \bmod p}^{m \bmod 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\n",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\n",C(n,m)*d[n-m]%mo);
}
return 0;
}