文章目录
数论(三)
高斯消元
即用线性代数的方式解决n元一次方程组
思路:
将n元一次方程组转化为上三角形的形式
基本操作(不影响结果):
- 把某一行乘以一个非零的数
- 交换某两行
- 把某一行的若干倍加到另一行上去
步骤:
- 定义行r=0列c=0,系数矩阵
a[N][N]
- 从第r行c列开始整理这个方程组
- 找到第
c
列的最大值(绝对值
)所在的行t
,如果第c
列的最大值是0,则说明出现了无用约束等式(矩阵的秩r<行列式的个数n
),进行下一次循环continue
- 将第
t
行的数与第r
行交换- 将第
r
行的第c
列变为1,也就是第r行同除以a[r][c]
- 将第
r
行之后的所有行的第c
列变为0,也就是r+1~n
行应该减去其第c列元素的值- r++,c++,重复步骤
结果:
- 如果
r<n (约束条件小于n)
,说明无解或者无穷多解,此时再分类讨论,如果出现等式0=!0
则无解,否则说明无用约束全部是0=0,那么有无穷多解r=n
有唯一解 ,此时处理系数矩阵成答案的形式
883高斯消元解线性方程组
#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
const double eps=1e-6;
const int N=110;
int n;
double a[N][N]; //系数数组
int gauss(){
int r,c; //定义行列,r同时也代表矩阵的秩
for(r=0,c=0;c<n;c++){ //从0行0列开始枚举,如果某一列全部为0,r不会++,(线性代数中相当于有一行式子无效) ,注意此处c<n而不是c<=n,不枚举常数列
//找当前列数值最大的那一行
int t=r;
for(int i=t+1;i<n;i++)
if(fabs(a[i][c])>fabs(a[t][c]))t=i; //系数有可能有负数,使用绝对值比较大小
if(fabs(a[t][c])<eps)continue; //如果最大值是0,说明出现无用约束等式,跳过
//把找到的那一行移动到第r行(相对不动行的最顶行),c列之前列的数值都是0,不需要再交换
for(int i=c;i<=n;i++)swap(a[r][i],a[t][i]);
//把第r行第一个数处理成1,从后往前处理否则a[r][c]会发生变化
for(int i=n;i>=c;i--)a[r][i]/=a[r][c];
//把r+1到n行全部减掉相应倍数,使每行第c个数是0
for(int i=r+1;i<n;i++)
for(int j=n;j>=c;j--)a[i][j]-=a[r][j]*a[i][c]; //这一步也是从后往前处理,不需要除以a[r][c]因为a[r][c]已经是1
r++; //如果continue,r不加,r也代表有用的约束等式
}
//如果约束等式小于n只可能是无解或者无穷多解
if(r<n){
for(int i=r;i<n;i++)if(fabs(a[i][n])>eps)return 2; //如果存在一个(可能有多个)无效约束等式的b不为0,说明无解
return 1;
}
//有解,处理系数矩阵,每一行的最后一个元素由下一行的最后一个元素对应位置的倍数相消
for(int i=n-2;i>=0;i--)
for(int j=i+1;j<n;j++)
a[i][n]-=a[j][n]*a[i][j];
return 0;
}
int main(){
cin>>n;
for(int i=0;i<n;i++)
for(int j=0;j<=n;j++)
cin>>a[i][j];
int t=gauss();
if(t==0){
for(int i=0;i<n;i++)printf("%.2lf\n",a[i][n]);
}
if(t==1)cout<<"Infinite group solutions";
if(t==2)cout<<"No solution";
return 0;
}
884高斯消元解异或线性方程组
思路:
- 与高斯消元的思路一致,将异或线性方程组转换成上三角的形式
- 根据矩阵的秩判断是否有解
- 与高斯消元不同的是,要将某一行的系数削成0,只需要使用异或操作即可
步骤:
- 定义行列
r
,c
按列枚举- 找到当前列不为0的一行,将它交换到第一行
- 将除了这一行以外的其余所有行的当前列削成0(
异或
)- 重复步骤
- 根据矩阵的秩枚举结果
#include<iostream>
#include<cmath>
#include<algorithm>
using namespace std;
const int N=110;
int a[N][N];
int n;
int gauss(){
int r,c;
for(r=0,c=0;c<n;c++){
int t=r;
for(int i=r;i<n;i++) //找到不为0的一行
if(a[i][c]){
t=i;
break;
}
if(!a[t][c])continue; //如果该列全部为0,则出现秩小于n的情况
if(t!=r){
for(int i=c;i<=n;i++)swap(a[t][i],a[r][i]);
}
for(int i=r+1;i<n;i++){
if(a[i][c])for(int j=c;j<=n;j++)a[i][j]^=a[r][j]; //将该列其余 首元素不为0的行清0
}
r++;
}
if(r<n){
for(int i=r;i<n;i++)if(a[i][n])return 0; //讨论秩小于n的情况
else return 1;
}
else{
for(int i=n-1;i>=1;i--){
for(int j=i-1;j>=0;j--)a[j][n]^=a[j][i]*a[i][n]; //按列相消,和上面略有区别
}
return 2;
}
}
int main(){
cin>>n;
for(int i=0;i<n;i++)
for(int j=0;j<=n;j++)cin>>a[i][j];
int t=gauss();
if(t==0)cout<<"No solution";
else if(t==1)cout<<"Multiple sets of solutions";
else {
for(int i=0;i<n;i++){
cout<<a[i][n]<<endl;
}
}
}
组合数
- 性质:
C ( n m ) = C ( n m − 1 ) + C ( n − 1 m − 1 ) C\tbinom{n}{m}=C\tbinom{n}{m-1}+C\tbinom{n-1}{m-1} C(mn)=C(m−1n)+C(m−1n−1)
以下四道题目分别代表求组合数的四种方式
时间复杂度对应每一次询问的时间复杂度,由于是提前预处理,询问的时间复杂度应当和与处理的复杂度相加
10^5次
询问 ,二维矩阵预处理1<=b<=a<=2000
O ( n 2 ) O(n^2) O(n2) n=200010^4次
询问,快速幂预处理1<=b<=a<=10^5
O ( N l o g ( M o d ) ) O(Nlog(Mod)) O(Nlog(Mod)) Mod=1e9 +720次 询问
,卢卡斯定理预处理1<=b<=a<=10^18
O ( l o g P N ∗ l o g P ) O(log_{P}N*log{P}) O(logPN∗logP)约等于 O ( P l o g P ) O(PlogP) O(PlogP) Mod=P=1e51次询问
,分解质因数+高精度1<=b<=a<=5000
O O O
885求组合数I(n2)
方式一:直接预处理出0~2000的所有组合数(对),将他们存到二维数组中,直接通过查询方式获得结果
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
const int N=2010,mod=1e9+7;
int c[N][N];
void init(){
//初始化c[N][N]0~2000的所有组合数对形如c[0][0],c[1][0],c[2][0]...的应当等于1
for(int i=0;i<N;i++){
for(int j=0;j<=i;j++){ //初始化斜对角线的一半数据即可
if(!j)c[i][j]=1;
else c[i][j]=(LL)(c[i-1][j]+c[i-1][j-1])%mod; //相加时可能溢出,应当转化成longlong
}
}
}
int main(){
init();
int n;
cin>>n;
while(n--){
int a,b;
cin>>a>>b;
cout<<c[a][b]<<endl;
}
return 0;
}
886求组合数II(nlogn)
方式二:
用快速幂预处理出fact(阶乘
)数组以及infact(阶乘的逆元
)数组
fact[i]
表示 i ! m o d 1 0 7 + 1 i! \ mod \ 10 ^ 7 +1 i! mod 107+1infact[i]
表示 i ! − 1 m o d 1 0 7 + 1 i! \ ^ {-1} \ mod \ 10 ^ 7 +1 i! −1 mod 107+1fact[i]
和infact[i]
互为倒数- 最后用递推公式求得结果
- 逆元采用快速幂的方式来求, ( k ! ) − 1 (k!)^{−1} (k!)−1 = = = ( ( k − 1 ) ! ) − 1 × k − 1 ((k−1)!)^{−1}×k^{−1} ((k−1)!)−1×k−1
- 组合数就等于对应数组相乘的值,避免了除法运算
- 时间复杂度分析:a,b,上限N(105)乘以快速幂时间复杂度log(mod)
- N l o g ( m o d ) + N Nlog(mod)+N Nlog(mod)+N约等于10万
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
const int N=1e5+10,mod=1e9+7;
int fact[N],infact[N];
//快速幂用于求逆元
int qmi(int a,int k){
int res=1;
while(k){
if(k&1)res=(LL)res*a%mod;
a=(LL)a*a%mod;
k=k>>1;
}
return res;
}
int main(){
fact[0]=infact[0]=1;
//预处理出i!%mod 和i!^-1 %mod
for(int i=1;i<N;i++){
fact[i]=(LL)fact[i-1]*i%mod;
infact[i]=(LL)infact[i-1]*qmi(i,mod-2)%mod;
}
int n;
cin>>n;
while(n--){
int a,b;
int res;
cin>>a>>b;
res=(LL)fact[a]*infact[b]%mod*infact[a-b]%mod; //递推公式算出结果
//注意不能写成简化公式res=(LL)fact[a-b+1]*infact[b]%mod;
cout<<res<<endl;
}
}
887求组合数III(plogn)
卢卡斯定理: C ( b a ) ≡ C ( b m o d p a m o d p ) ∗ C ( b / p a / p ) C\tbinom{b}{a} \equiv C\tbinom{b\ mod\ p}{a\ mod \ p} *C\tbinom{b / p}{a / p} C(ab)≡C(a mod pb mod p)∗C(a/pb/p)
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
int p;
int qmi(int a,int k){ //快速幂求逆元
int res=1;
while(k){
if(k&1)res=(LL)res*a%p;
k>>=1;
a=(LL)a*a%p;
}
return res;
}
int c(int a,int b){ //定义求组合数
int res=1;
for(int i=a,j=1; j<=b ;i--,j++){ //即a*(a-1)*...*(a-b+1)/b! 此处b求逆元
res=(LL)res*i%p;
res=(LL)res*qmi(j,p-2)%p;
}
return res;
}
int lucas(LL a,LL b){
if(a<p&&b<p)return c(a,b); //ab均小于p,返回定义求逆元
else return (LL)c(a%p,b%p)*lucas(a/p,b/p)%p; //否则对a,b降阶
}
int main(){
int n;
cin>>n;
while(n--){
LL a,b;
cin>>a>>b>>p;
cout<<lucas(a,b)<<endl;
}
}
888求组合数IV **
- 将组合数拆解成质因数相乘的形式,这样只利用到高精度乘法而不用高精度除法
- 例如计算 C 30 20 C_{30}^{20} C3020将30的质因数(2,3,5,7,11…29)筛出来 那么组合数可以表示成
- 2 a 1 × 3 a 2 × 5 a 3 × . . . × 2 9 a n 2^{a1} \times 3^{a2}\times 5^{a3}\times...\times29^{an} 2a1×3a2×5a3×...×29an
- 其中次数的计算 如
30!
中2的次数应该是
:由 ⌊ 30 / 2 ⌋ + ⌊ 30 / 2 2 ⌋ + ⌊ 30 / 2 3 ⌋ + ⌊ 30 / 2 4 ⌋ \lfloor 30/2 \rfloor+\lfloor 30/2^2 \rfloor+\lfloor 30/2^3 \rfloor+\lfloor 30/2^4 \rfloor ⌊30/2⌋+⌊30/22⌋+⌊30/23⌋+⌊30/24⌋ (2^5>30所以不除
- 而总的次数应该
由上下阶乘共同决定
如 a 3 = ⌊ 30 / 5 ⌋ + ⌊ 30 / 5 2 ⌋ − ⌊ 20 / 5 ⌋ − ⌊ ( 30 − 20 ) / 5 ⌋ a_3= \lfloor 30/5 \rfloor+\lfloor 30/5^2 \rfloor - \lfloor 20/5\rfloor -\lfloor (30-20)/5\rfloor a3=⌊30/5⌋+⌊30/52⌋−⌊20/5⌋−⌊(30−20)/5⌋
算法:
- 找到较大数a(1~a)中所有的质数因子 (线性筛质因数)
- 将各个质数因子对应的次数计算出来
- 利用高精度乘法将各项相乘得出答案
#include<iostream>
#include<vector>
using namespace std;
const int N=5010;
int primes[N],st[N],cnt;
int sums[N];
//求1~n质因数
void get_primes(int n){
for(int i=2;i<=n;i++){
if(!st[i])primes[cnt++]=i;
for(int j=0;primes[j]<=n/i;j++){
st[primes[j]*i]=true;
if(i%primes[j]==0)break;
}
}
}
//求各个质因数的系数
int get(int n,int p){
int res=0;
while(n){
res+=n/p;
n/=p;
}
return res;
}
vector<int> mul(vector<int> a, int b){
vector<int> res;
int t=0;
for(int i=0;i<a.size()|t;i++){
if(i<a.size())t=a[i]*b+t;
res.push_back(t%10);
t/=10;
}
return res;
}
int main(){
int a,b;
cin>>a>>b;
get_primes(a);
for(int i=0;i<cnt;i++){
int p=primes[i];
sums[i]=get(a,p)-get(b,p)-get(a-b,p);
}
vector<int> res;
res.push_back(1);
for(int i=0;i<cnt;i++) //对于每一个质因子`primes[i]`都要乘以他的对应次数`sums[i]`次
for(int j=0;j<sums[i];j++)
res=mul(res,primes[i]);
for(int i=res.size()-1;i>=0;i--)cout<<res[i];
return 0;
}
889.满足条件的01序列
算法样例:
- 设
n=6
,将每一种方案组合映射到二维空间的路径,规定数字0代表向右走一格
,数字1代表向上走一格
- 那么无论满不满足条件的路径都会以
(6,6)
为终点,其中总的路径数应当为 C 12 6 C_{12}^6 C126(代表选6次向上走)- 我们的目的是求出其中不满足条件的方案,任意前缀中的0>=1的个数,对应到二维空间,即为
对于任意一个点(x,y),x>=y
即经过红线以及红线以上的点
的路径均不满足条件- 我们假设一条不满足条件的路径,那么路径一定经过红线,并最终到达(6,6),我们以这条红线为对称轴,将终点(6,6)对应到另外一个终点(5,7),
- 所以所有不满足条件的路径经过对称一定可以走到另一个终点(5,7),而所有经过(5,7)的路径一定经过了红线,所以到达(5,7)的路径全部是不满足条件的路径,最终满足条件的路径就是 C 12 6 − C 12 5 = 1 7 C 12 6 C_{12}^6-C_{12}^{5}=\frac{1}{7}C_{12}^{6} C126−C125=71C126
卡特兰数: C 2 n n − C 2 n n − 1 = 1 n + 1 C 2 n n C_{2n}^{n}-C_{2n}^{n-1}= \frac{1}{n+1}C_{2n}^{n} C2nn−C2nn−1=n+11C2nn
- C 2 n n − C 2 n n − 1 = ( 2 n ) ! n ! ∗ n ! − ( 2 n ) ! ( n + 1 ) ! ∗ ( n − 1 ) ! = ( n + 1 ) ∗ ( 2 n ) ! − n ∗ ( 2 n ) ! n ! ∗ ( n + 1 ) ! = ( 2 n ) ! n ! ∗ ( n + 1 ) ! = 1 ( n + 1 ) ∗ ( 2 n ) ! n ! ∗ n ! = 1 ( n + 1 ) C 2 n n C_{2n}^{n}-C_{2n}^{n-1}= \frac{(2n)!} {n!*n!} -\frac{(2n)!}{(n+1)!*(n-1)!}=\frac{(n+1)*(2n)!-n*(2n)!}{n!*(n+1)!}=\frac {(2n)!}{n!*(n+1)!}=\frac{1}{(n+1)}*\frac{(2n)!} {n!*n!}=\frac{1}{(n+1)}C_{2n}^{n} C2nn−C2nn−1=n!∗n!(2n)!−(n+1)!∗(n−1)!(2n)!=n!∗(n+1)!(n+1)∗(2n)!−n∗(2n)!=n!∗(n+1)!(2n)!=(n+1)1∗n!∗n!(2n)!=(n+1)1C2nn
#include<iostream>
using namespace std;
const int mod=1e9+7;
typedef long long LL;
int qmi(int a,int k){
int res=1;
while(k){
if(k&1)res=(LL)res*a%mod;
k>>=1;
a=(LL)a*a%mod;
}
return res;
}
int main(){
int n;
cin>>n;
int a=2*n,b=n;
int res=1;
//定义法求组合数
for(int i=a,j=1;j<=b;j++,i--){
res=(LL)res*i%mod;
res=(LL)res*qmi(j,mod-2)%mod;
}
res=(LL)res*qmi(n+1,mod-2)%mod;
cout<<res;
}