OI常用数学定理&方法总结
组合数计算($O(n)$)
https://www.cnblogs.com/linzhuohang/p/11548813.html
矩阵行列式计算
先用高斯消元将矩阵消为上三角矩阵,其行列式的值就是主对角线的乘积(就是最长的那条)
复杂度$O(n^3)$
Lucas定理
如果要计算很大的组合数,但模数较小,考虑这个方法
对于质数p,$C_n^m ≡ C_{n/p}^{m/p}*C_{n \ mod \space p}^{m \ mod \space p} (mod \space p)$
这样我们可以预处理出p以内的组合数
然后不断地将n,m除以p迭代
就可以$O(p+log_p^n)$内计算很大的组合数
证明
证明来自https://www.luogu.com.cn/blog/_post/122200
代码
#include <iostream> #include <cstdio> using namespace std; #define mod 10007 #define int long long #define c(n,m) (fact[n]*ifac[m]%mod*ifac[n-m]%mod) int fact[mod+10],ifac[mod+10]; int lucas(int n,int m) { if(n<m) return 0; int s=n/mod,t=m/mod,p=n%mod,q=m%mod; if(s==0&&t==0) return c(n,m); return (lucas(s,t)*lucas(p,q))%mod; } int qpow(int a,int b) { int ans=1; while(b) { if(b&1) ans*=a,ans%=mod; a*=a; a%=mod; b>>=1; } return ans; } signed main() { int t; cin>>t; fact[0]=1; for(int i=1;i<mod;i++) fact[i]=fact[i-1]*i%mod; ifac[mod-1]=qpow(fact[mod-1],mod-2); for(int i=mod-2;i>=0;i--) ifac[i]=ifac[i+1]*(i+1)%mod; while(t--) { int n,m; scanf("%lld%lld",&n,&m); printf("%lld\n", lucas(n,m)); } }
中国剩余定理(CRT)
这个是用来将一些要求模数为质数的方法(如上文的lucas,exgcd等)扩展成模数为任意数的方法
先将模数p拆成若干个质数$m_1*m_2...m_k$
然后逐个计算答案$a_i$
设原答案为x.
则我们其实就是要计算如下方程的解x
上述方程组有整数解。并且在模$M=\prod m_i$下的解是唯一的,解为
$(\sum a_i*M_i*M_i^{-1})mod \ M$
其中$M_i=M/mi$,而$M_i^{-1}$为$M_i$模$m_i$的逆元。
复杂度为$O(k)$
证明
对于任意的一个i,$a_i*M_i*M_i^{-1}$
因为 $M_i*M_i^{-1} ≡ 1 (mod \space m_i)$
所以$a_i*M_i*M_i^{-1} ≡ a_i (mod \space m_i)$
对于任意一个$j!=i$
因为$M_i$中含有$m_j$
所以 $a_i*M_i*M_i^{-1}≡ 0 (mod \space m_j)$
这样全部加起来对于每一个$m_i$都能满足
代码
int prim[5]={0,3,5,6793,10007},ret[5]/*上文的ai*/,mi[5]/*上文的Mi*/,invm[5]/*上文的Mi的逆元*/; int tot=1,ans=0,p=3*5*6793*10007; for(int i=1;i<=4;i++) ret[i]=solve(prim[i]); for(int i=1;i<=4;i++) mi[i]=p/prim[i],invm[i]=qpow(mi[i],prim[i]-2,prim[i]); for(int i=1;i<=4;i++) ans+=ret[i]*mi[i]%p*invm[i]%p,ans%=p; printf("%lld\n", ans);
BSGS (大步小步法)
这个可以在$O(\sqrt{n})$的时间内求解最小的x满足$a^x≡b(mod \ p)\ \ gcd(a,p)=1$(限制是为了满足费马小定理的限制)
图来自https://www.cnblogs.com/SGCollin/p/9988366.html
正确性证明
因为$a^{p-1}≡ 1(mod \ p)$ (费马小定理)
所以x一定在$[0,p-2]$内
而m=$\sqrt{p}$保证了$[0,p-2]$内每一个数都能被遍历到
代码
#include <iostream> #include <cstring> #include <cstdio> #include <vector> #include <cmath> using namespace std; #define mk make_pair #define mod 233333 #define pr pair<int,int> #define int long long vector<pr > vec[mod+10]; int p; int qpow(int a,int b) { int ret=1; while(b) { if(b&1) ret*=a,ret%=p; a*=a; a%=p; b>>=1; } return ret; } bool work() { int b,n; if(scanf("%lld%lld%lld",&p,&b,&n)==EOF) return false; int m=ceil(sqrt(p)); for(int i=1;i<=m;i++) { int t=n*qpow(b,i)%p; //cout<<i<<endl; vec[t%mod].push_back(mk(t,i)); } int ans=-1; for(int i=1;i<=m;i++) { int t=qpow(b,i*m)%p; for(int j=vec[t%mod].size()-1;j>=0;j--) if(vec[t%mod][j].first==t) { ans=i*m-vec[t%mod][j].second; break; } if(ans!=-1) break; } if(ans!=-1) printf("%lld\n",ans); else printf("no solution\n"); return true; } signed main() { while(work()) memset(vec,0,sizeof(vec)); }
矩阵-树定理
来自https://www.cnblogs.com/yangsongyi/p/10697176.html
这个定理共分为三个部分:
1.给出无向图,求这个图的生成树个数。
2.给出有向图和其中的一个点,求以这个点为根的生成外向树个数。
3.给出有向图和其中一个点,求以这个点为根的生成内向树个数。
部分一:我们对这个图构造两个矩阵,分别是这个图的连通矩阵和度数矩阵。连通矩阵S1的第i行第j列上的数字表示原无向图中编号为i和编号为j的两个点之间的边的条数。度数矩阵S2S2只有斜对角线上有数字,即只有第i行第i列上有数字,表示编号为i的点的度数是多少。我们将两个矩阵相减,即S2−S1,我们记得到的矩阵为T,我们将矩阵T去掉任意一行和一列(一般情况去掉最后一行和最后一列的写法比较多)得到T′,最后生成树的个数就是这个矩阵T′的行列式。
部分二:我们对这个图构造两个矩阵,分别是这个图的连通矩阵和度数矩阵。连通矩阵S1的第i行第j列上的数字表示原无向图中编号为i和编号为j的两个点之间编号i的点指向编号为j的点的条数。度数矩阵S2只有斜对角线上有数字,即只有第i行第i列上有数字,表示编号为i的点的入度是多少。我们将两个矩阵相减,即S2−S1,我们记得到的矩阵为T,我们将矩阵T去掉根所在行和根所在列得到T′,最后生成树的个数就是这个矩阵T′的行列式。
部分三:我们对这个图构造两个矩阵,分别是这个图的连通矩阵和度数矩阵。连通矩阵S1的第i行第j列上的数字表示原无向图中编号为i和编号为j的两个点之间编号i的点指向编号为j的点的条数。度数矩阵S2只有斜对角线上有数字,即只有第i行第i列上有数字,表示编号为ii的点的出度是多少。我们将两个矩阵相减,即S2−S1,我们记得到的矩阵为T,我们将矩阵T去掉根所在行和根所在列得到T′,最后生成树的个数就是这个矩阵T′的行列式。
证明
这个记就好了。。。我也不会证