NOIP 数学

Day 1

mod

(a+b)%c=(a%c+b%c)%c
(a-b)%c=(a%c-b%c)%c=((a-b)%c+c)%c
(a*b)%c=(a%c*b%c)%c=(1ll*a*b)%c//1ll=long long 类型的1 
//求证((a%c+b%c)%c)=(a + b)%c:
a=x*c+x',b=y*c+y'
a%c=x',b%c=y'
(a+b)%c=(x*c+x'+y*c+y')%c=(x'+y')%c
a%c+b%c=x'+y'
((a%c+b%c)%c)=(x'+y')%c
((a%c+b%c)%c)=(a+b)%c

gcd&&lcm

注意:gcd(a,0) = a

int gcd(int a,int b){
	if (b==0) return a;
	return gcd(b,a%b);
}//最大公因数
lcm(a,b)=a/gcd(a,b)*b//最小公倍数

快速幂&&快速乘

有的时候O(n)也过不了,啊啊啊啊

int ksm(int x,int y,int p){//计算x^y%p
	if(y==0)return 1;
	int z=ksm(x,y/2,p);
	//int z=ksm(x,y>>1,p);
	z=1ll*z*z%p;
	if(y%2==1) z=1ll*z*x%p;
	//if(y&1) z=1ll*z*x%p;
	return z;
}
int ksc(int x,int y,int p){//计算x*y%p
	if(y==0)return 0;
	int z=ksc(x,y/2,p);
	//int z=ksm(x,y>>1,p);
	z=(z+z)%p;
	if (y%2==1)z=(z+x)%p;
	//if(y&1) z=(z+x)%p;
	return z;
}

矩阵

对矩阵进行加法的重载运算符:

inline Matrix operator+(const Matrix &a,const Matrix &b){//矩阵加法 
    Matrix res=Matrix(a.n,a.m);
    rep(i,0,a.n-1,1) rep(j,0,a.m-1,1)
        res.val[i][j]=(a.val[i][j]+b.val[i][j])%MOD;
    return res;
}

对矩阵进行减法的重载运算符:

inline Matrix operator-(const Matrix &a,const Matrix &b){//矩阵减法 
    Matrix res=Matrix(a.n,a.m);
    rep(i,0,a.n-1,1) rep(j,0,a.m-1,1)
        res.val[i][j]=(a.val[i][j]-b.val[i][j])%MOD;
    return res;
}

对矩阵进行乘法的重载运算符:

matrix operator*(const matrix &m1,const matrix &m2){//重载运算符,对*进行重新定义,只针对矩阵*矩阵,普通乘法不影响
	matrix m3;
	m3.n=m1.n;m3.m=m2.m;
	for(int i=1;i<=m3.n;i++)
		for(int j=1;j<=m3.m;j++)
			for(int k=1;k<=m1.m;k++)
				m3.z[i][j]+=m1.z[i][k]*m2.z[k][j];
	return m3;
}

矩阵快速幂:

matrix ksm(matrix x,int y){//计算x矩阵的y次方 
	if(y==1) return x;
	matrix z=ksm(x,y/2);
	z=z*z;
	if(y%2==1) z=z*x;
	return z;
} 

【补充:定义矩阵:

struct matrix{//定义一个矩阵 
	int n,m;
	int z[10][10];
	matrix(){
		n=m=0;
		memset(z,0,sizeof(z));
	}
}; 

分解质因数

void factorize(int x){//对x分解质因数
	int cnt; 
	for (int i=2;i*i<=x;i++)
		if (x%i==0){
			cnt++;
			prime[cnt]=a;
			while(x%i==0){
				num[cnt]++;
				x/=a;
			}
		}
	if (x!=1){
		cnt++;
		prime[cnt]=x;
		num[cnt]=1;
	}
}

Day 2

exgcd

/*
	题目:给定一个a,b和g(g=gcd(a,b))
	求出两个数x,y,使得xa+yb=g,求出任意一组x,y的解即可
	gcd(a,b)=gcd(b,a%b)=g 
	-----xa+yb=g
	|	 x'b+y'(a%b)=g
	|    x'b+y'(a-b*(a/b))=g
	|    x'b+y'a-y'b*(a/b)=g
	-----y'a+(x'-y'*(a/b))*b=g
	∴x=y'   y=(x'-y'*(a/b))
*/ 
int exgcd(int a,int b,int &x,int &y){//扩展欧几里得算法 
	//求g=gcd(a,b)
	//以及xa+yb=g 
	if(b==0){
		x=1,y=0;
		return a;
	}
	int xp,yp;
	int g=exgcd(b,a%b,xp,yp);
	//xp*b+yp*(a%b)=g
	//xp*b+yp*(a-b*(a/b))=g
	//xp*b+yp*a-yp*b*(a/b)=g
	//yp*a+(xp-yp*(a/b))*b=g
	x=yp;
	y=xp-yp*(a/b);
	return g;
}

miller_rabin

#include<bits/stdc++.h>
using namespace std;
int ksm(int x,int y,int p)
//计算x^y%p
{
	if (y==0) return 1;
	int z=ksm(x,y/2,p);
	z = 1ll * z * z % p;
	if (y%2==1) z = 1ll * z * x % p;
	return z;
}
/*
	如果n为质数,n-1=d*2^r
	例:n=37则d=9,r=2;
	则至少成立一下一条性质:
	1.a^d%n=1
	2.存在0<=i<r   a^(d*2^i)%n=n-1
	[如果n不是质数也有可能成立] 
*/
bool miller_rabin(int n,int a){
	int d=n-1;
	int r=0;
	while(d%2==0)
		d=d/2,r++;
	int x=ksm(a,d,n);
	if(x==1)return true;
	for(int i=0;i<r;i++){
		if(x==n-1)return true;
		x=1ll*x*x%n;
	}
	return false;
} 
int prime_list[]={2,3,5,7,13,23,37,73};
bool is_prime(int n){
	if(n<2)return false;
	for(int i=0;i<8;i++){
		if(n==prime_list[i])return true;
		if(n%prime_list[i]==0)return false;
		if(!miller_rabin(n,prime_list[i]%n))return false;
	}
	return true;
}

欧拉定理&&欧拉函数&&费马小定理

#include<bits/stdc++.h>
using namespace std;
int get_phi(int n){
	int phi=n;
	for(int i=2;i*i<=n;i++)
		if(n%i==0){
			phi=phi/i*(i-1);
			while(n%i==0)
				n=n/i;
		}
	if(n!=1)phi=phi/n*(n-1);
	return phi;//phi代表1~n中与n互质的数的个数 
}
int ksm(int x,int y,int p)
//计算x^y%p
{
	if (y==0) return 1;
	int z=ksm(x,y/2,p);
	z = 1ll * z * z % p;
	if (y%2==1) z = 1ll * z * x % p;
	return z;
}
long long n,p;
long long fac[1005],ifac[1005],inv[1005]; 
int main(){
	/*
	加法:(3+4)%5=2
	减法:(3-4)%5:C++里为-1,而数学中为4
	乘法:(3*4)%5=2
	除法:(3/4)%5=?
	逆元:
		原式:a/b%P
		找到一个c使a*c%P=a/b%P
		把c称为b的逆元
		
		费马小定理:
		a^(P-1)≡1 (mod P) 
		条件:1.P是质数  2.gcd(a,P)=1 
		例:P=5,a=4  
		证明:a^(P-1)%P=4^4%5=256%5=1
		等式两边同乘1/a,得a^(P-2)≡1/a(mod P)
		代入原式:3/4%5
				=3*(1/4)%5
				=3*4^(5-2)%5
				=2
		公式:a/b%P---->1ll*a*ksm(b,P-2,P)%P 
		
		如果P不是质数呢?
		例:5/8%9 
		那么我们就需要一个新的定理——欧拉定理:
		a^(φ(P))≡1(mod P)
		条件:gcd(a,P)=1
		φ(P)欧拉函数,代表1~P中有多少个数和P互质
		例:φ(4)=2 [1,3]  φ(6) [1,5] 
		转化:a^(φ(P)-1)≡1/a(mod P) 
		代入原式:5/8%9
				=5*8^(6-1)%9 
		公式:a/b%P--->1ll*a*ksm(b,φ(P)-1,P)%P 
				
		φ(n)怎么算呢?
		如果暴力的话时间复杂度是O(n logn) 
		设P1是n的唯一一个质因子 
		1.当n=P1时      φ(n)=P1-1
		2.当n=P1^2时    φ(n)=P1^2-P1=P1(P1-1)
		3.当n=P1^k1时   φ(n)=(P1-1)/P1*n=n*(1-1/P1)
		如果有多个质因子
		两个因子:n=P1*P2         φ(n)=n-n/P1-n/P2+n/P1*P2
							=n*(1-1/P1)*(1-1/P2)
		t个因子:n=P1^k1*P2^k2*……Pt^kt 
		所以:φ(n)=n*				
							
							
		例题:求1~n中的每个数%p的逆元
	   	   1          2         3      ……        n 
		1^(P-2)    2^(P-2)   3^(P-2)   ……      n^(P-2)
		*/
		
		//第一种方法: 
		cin>>n>>p;
		//fac[i]=i!
		fac[0]=1;
		for(int i=1;i<=n;i++)
			fac[i]=1ll*fac[i-1]*i%p;//算阶乘 
		//ifac[i]=1/i! i!的逆元
		ifac[n]=ksm(fac[n],p-2,p);
		for(int i=n-1;i>=0;i--)
			ifac[i]=1ll*ifac[i+1]*(i+1)%p;//算阶乘的逆元 
		//inv[i]=1/i i的逆元 
		//1/v=(i-1)!/i!
		for(int i=1;i<=n;i++)
			inv[i]=1ll*fac[i-1]*ifac[i]%p;//求每个数的逆元 
		for(int i=1;i<=n;i++){
			cout<<inv[i]<<endl;
		}
}

例题1:
求φ(1)~φ(n)每个数的值

	phi[1]=1;
	for(int i=2;i<=n;i++){
		if(not_prime[i]==false){
			cnt++;
			prime[cnt]=i;
			phi[i]=i-1;
		}
		for(int j=1;j<=cnt;j++){
			int x=prime[j]*i;//筛掉第j个质数的i倍
			if(x>n)break;
			not_prime[x]=true;
			if(i%prime[j]==0){
				phi[x]=prime[j]*phi[i];//积性函数的性质
				break; 
				//每个数只被最小的质因子筛掉  
			}
			phi[x]=phi[prime[j]]*phi[i];//线性筛的性质 
	}
	//线性筛可以求积性函数 

例题2: 求每个数的逆元

First way:

#include<bits/stdc++.h>
using namespace std;
int ksm(int x,int y,int p)
//计算x^y%p
{
	if (y==0) return 1;
	int z=ksm(x,y/2,p);
	z = 1ll * z * z % p;
	if (y%2==1) z = 1ll * z * x % p;
	return z;
}
long long n,p;
long long fac[1005],ifac[1005],inv[1005]; 
int main(){		
		/*例题:求1~n中的每个数%p的逆元
	   	   1          2         3      ……        n 
		1^(P-2)    2^(P-2)   3^(P-2)   ……      n^(P-2)
		*/
		//第一种方法: 
		cin>>n>>p;
		//fac[i]=i!
		fac[0]=1;
		for(int i=1;i<=n;i++)
			fac[i]=1ll*fac[i-1]*i%p;//算阶乘 
		//ifac[i]=1/i! i!的逆元
		ifac[n]=ksm(fac[n],p-2,p);
		for(int i=n-1;i>=0;i--)
			ifac[i]=1ll*ifac[i+1]*(i+1)%p;//算阶乘的逆元 
		//inv[i]=1/i i的逆元 
		//1/v=(i-1)!/i!
		for(int i=1;i<=n;i++)
			inv[i]=1ll*fac[i-1]*ifac[i]%p;//求每个数的逆元 
		for(int i=1;i<=n;i++){
			cout<<inv[i]<<endl;
		}
		return 0;
}

Second way:

#include<bits/stdc++.h>
using namespace std;
void solve(int p1,int a1,int p2,int a2,int &p,int &a){
	//x%p1=a1
	//x%p2=a2
	//x%p=a
	if(p1<p2)swap(p1,p2),swap(a1,a2);
	int x=a1;
	int g=__gcd(p1,p2);
	int l=p1/g*p2;
	while(x<=l&&x%p2!=a2)
		x+=p1;
	if(x>l)p=-1,a=-1;
	else p=l,a=x;
} 
int main(){
	int n,p1,p2,a1,a2,p,a=1,x;
	cin>>n;
	cin>>p1>>a1;
	for(int i=2;i<=n;i++){
		cin>>p2>>a2;
		solve(p1,a1,p2,a2,p,a);
		p1=p2,a1=a2;
	}
	cout<<a<<endl;
} 

同余方程

/*
	题目:给定一个a,b和g(g=gcd(a,b))
	求出两个数x,y,使得xa+yb=g,求出任意一组x,y的解即可
	gcd(a,b)=gcd(b,a%b)=g 
	-----xa+yb=g
	|	 x'b+y'(a%b)=g
	|    x'b+y'(a-b*(a/b))=g
	|    x'b+y'a-y'b*(a/b)=g
	-----y'a+(x'-y'*(a/b))*b=g
	∴x=y'   y=(x'-y'*(a/b))
*/ 
#include<bits/stdc++.h>
using namespace std;
int exgcd(int a,int b,int &x,int &y){
	//求g=gcd(a,b)
	//以及xa+yb=g 
	if(b==0){
		x=1,y=0;
		return a;
	}
	int xp,yp;
	int g=exgcd(b,a%b,xp,yp);
	//xp*b+yp*(a%b)=g
	//xp*b+yp*(a-b*(a/b))=g
	//xp*b+yp*a-yp*b*(a/b)=g
	//yp*a+(xp-yp*(a/b))*b=g
	x=yp;
	y=xp-yp*(a/b);
	return g;
}
int main(){
	int x,y,a,b;
	cin>>a>>b;
	y=1;
	exgcd(a,b,x,y);
	if(x%b>=0)cout<<x%b<<endl;
	else cout<<(x%b+b)%b<<endl;
} 

质数筛法

//判断质数

//第一种做法 O(n log n): 
	for(int i=1;i<=n;i++)
		for(int j=i+i;j<=n;j+=i)
			not_primne[b]=true;
	for(int i=2;i<=n;i++)
		if(not_prime[a]==false)prime[++cnt]=a;
	
//第二种做法 O(n log(log n))≈O(n):
//只需枚举质数的倍数:
	for(int i=1;i<=n;i++)
		if(not_prime[a]==false) 
			for(int j=i+i;j<=n;j+=i)
				not_primne[b]=true;

//第三种做法 O(n)				 
//线性筛:只在每个数的最小质因子的位置筛掉该数
	for(int i=2;i<=n;i++){
		if(not_prime[i]==false){
			cnt++;
			prime[cnt]=i;
		}
		for(int j=1;j<=cnt;j++){
			int x=prime[j]*i;//筛掉第j个质数的i倍
			if(x>n)break;
			not_prime[x]=true;
			if(i%prime[j]==0)break;//每个数只被最小的质因子筛掉  
	}
/*  积性函数:
	有一个函数为f,当gcd(a,b)1(即a,b互质) f(a)*f(b)=f(a*b)
	φ就是积性函数 
	φ(nm)=nm*(1-1/p1)*(1-1/p2)*……*(1-1/pt)*(1-1/q1)*(1-1/q2)*……*(1-1/qw)
	完全积性函数(积性函数除去互质的条件): 
*/

中国剩余定理

扩展欧几里得算法:

/*
	---x%p1=a1
	|
	---x%p2=a2
	x=k1*p1+a1=k2*p2+a2
	k1*p1-k2*p2=a2-a1
	扩展欧几里得算法:xa+yb=g 
*/
#include<bits/stdc++.h>
using namespace std;
int exgcd(int a,int b,int &x,int &y){//扩展欧几里得算法 
	//求g=gcd(a,b)
	//以及xa+yb=g 
	if(b==0){
		x=1,y=0;
		return a;
	}
	int xp,yp;
	int g=exgcd(b,a%b,xp,yp);
	//xp*b+yp*(a%b)=g
	//xp*b+yp*(a-b*(a/b))=g
	//xp*b+yp*a-yp*b*(a/b)=g
	//yp*a+(xp-yp*(a/b))*b=g
	x=yp;
	y=xp-yp*(a/b);
	return g;
}
void solve(int p1,int a1,int p2,int a2,int &p,int &a){
	//x%p1=a1
	//x%p2=a2
	//x%p=a
	//x=k1*p1+a1=k2*p2+a2
	//k1*p1-k2*p2=a2-a1
	int g,k1,k2;
	g=exgcd(p1,p2,k1,k2);
	//k1*p1+k2*p2=g
	k2=-k2;
	//k1*p1-k2*p2=g
	//算一下a2-a1是g的多少倍
	if((a2-a1)%g!=0)p=-1,a=-1;
	else {
		int k=(a2-a1)/g;
		k1=k1*k;
		k2=k2*k;
		//k1*p1-k2*p2=a2-a1;
		int x=k1*p1+a1;
		p=p1/g*g;
		a=(x%p+p)%p;
	}
} 
int main(){
	int p,p1,p2,a,a1,a2;
	int n;
	cin>>n;
	cin>>p1>>a1;
	for(int i=2;i<=n;i++){
		cin>>p2>>a2;
		solve(p1,a1,p2,a2,p,a);
		p1=p2,a1=a2;
	}
	cout<<a<<endl;
}

大数翻倍法:

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>

using namespace std;

long long p,a;

long long gcd(long long a,long long b)
{
	if (!b) return a;
	else return gcd(b,a%b);
}

void solve(long long p1,long long a1,long long p2,long long a2,long long &p,long long &a)
{
	if (p1<p2) swap(p1,p2),swap(a1,a2);
	p = p1 / gcd(p1,p2) * p2;
	while (a1 <= p && a1 % p2 != a2)
		a1 += p1;
	if (a1>p) p=-1,a=-1;
	else a=a1;
}

int main()
{
	int n;
	cin >> n;
	cin >> p >> a;
	a%=p;
	for (int i=2;i<=n;i++)
	{
		long long pp,aa;
		cin >> pp >> aa;
		aa%=pp;
		solve(p,a,pp,aa,p,a);
	}
	cout << a << endl;
}

Day 3

BSGS(baby step giant step)

/*
	给你三个数a,b,p,其中p为质数
	求满足a^x%p=b的x的最小正整数解 
*/ 
#include<bits/stdc++.h>
using namespace std;
/*int solve(int a,int b,int p){//O(p)
	int v=1;
	for(int i=0;i<p-1;i++){//a^(p-1)=a^0
		if(v==b)return i;
		v=1ll*v*a%p;
		//if(v==1)return -1;//和a的0次方一样,可能会死循环 
	}
	return -1;
}
	a^0,a^1,a^2......a^(p-2)
	设s为每组的大小
	第一组:a^0,a^1,a^2......a^(s-1)
	第二组:a^s,a^(s+1),a^(s+2)......a^(2s-1)
	第三组:a^2s,a^(2s+1),a^(2s+2)......a^(3s-1)
	第一组暴力for循环寻找是否有数%p=b
	如果b在第二组出现了,那么他在第一组对应的位置就是b*a^(-s)
	查找第二组有没有b,就查找第一组是否有b*a^(-s) 
	查找其余的组中是否有b出现,只需查找第一组是否有对应的该数 
*/
int ksm(long long x,long long y,long long p){//计算x^y%p(快速幂)
	if(y==0) return 1;
	long long z=ksm(x,y>>1,p);//y>>1等同于y/2
	z=1ll*z*z%p;
	if(y&1) z=1ll*z*x%p;//y&1等同于y%2==1
	return z;
}
int solve(long long a,long long b,long long p){
	long long s=sqrt(p);
	long long v=1;
	set<int> se;
	for(long long i=0;i<s;i++){
		se.insert(v);
		v=1ll*v*a%p;
	}
	//O(p/s) 
	for(long long i=0;i*s<=p;i++){//看答案是否在第i行里面 
		//要看b*a^(-i*s)是否在第零行出现
		long long c=1ll*b*ksm(ksm(a,i*s,p),p-2,p)%p;
		if(se.count(c)!=0){
			long long v=ksm(a,i*s,p);//第i行的第一个数
			for(long long j=i*s;;j++){//O(s)
				if(v==b)return j;
				v=1ll*v*a%p;
			}//暴力在第i行寻找答案
		} 
	} 
	return -1;
}
//O(max(p/s,s))  所以s=sqrt(p) 
long long p,b,a;
int main(){
	cin>>a>>p>>b;
	while(a||b||p){
		a%=p;
		b%=p;
		long long l=solve(a,b,p);
		if(l==-1)cout<<"No solution"<<endl;
		else cout<<l<<endl;
		cin>>a>>p>>b;
	}
	/*cin>>p>>b>>n;
	long long l=solve(b,n,p);
	if(l==-1)cout<<"no solution"<<endl;
	else cout<<l<<endl;
	return 0;*/
}

排列&&组合

/*
	加法:同一阶段 
	乘法:不同阶段 
	
	排列&&组合:
	排列:
	n个人中选m个人去排队(考虑顺序) 
	P(n,m)=n*(n-1)*(n-2)*......*(n-m+1)
	简化: (n!)/((n-m)!)
	
	组合:
	n个人选m个人出去(不考虑顺序)
	C(n,m)=(n*(n-1)*(n-2)*......*(n-m+1))/m!
	简化:P(n,m)/m!
	即:n!/(m!*(n-m)!)
	性质:
	1.C(n,0)=C(n,n)=1
	2.C(n,m)=C(n,n-m)
	3.C(n,m)=C(n-1,m-1)+C(n-1,m)
	第一个物品选/不选,同一阶段,所以相加 
*/
	for(in i=0;i<=n;i++){
		C[i][0]=1;
		for(int j=1;j<=i;j++)
			C[i][j]=C[i-1][j-1]+C[i-1][j];
	} 
	//C数组其实就是一个杨辉三角()
	//从另一个方面说,杨辉三角中的每一个数都是一个组合数
/*
	性质4:C(n,0)+C(n,1)+C(n,2)+......+C(n,n)=2^n 
	选任意多个东西的方案数,每个都有选或不选两种可能,而两种选不选之间没有联系,所以用乘法 
	5.C(n,0)-C(n,1)+C(n,2)-C(n,3)+......=0;
	所有偶数位的和等于上一行所有位数的和,奇数也同理,所以加上偶数减去奇数,就相当于加上上一行再减去下一行,就是0
	6.把C(n,m)展开k次:
		C(n,m)=C(k,0)*C(n-k,m-k+0)
			  +C(k,1)*C(n-k,m-k+1)
			  +C(k,2)*C(n-k,m-k+2)
			  ...................
			  +C(k,k)*C(n-k,m-k+k) 
			  
	即:    k 
			Σ   C(k,i)*C(n-k,m-i) 
		   i=0	 
*/ 

二项式定理

/*
	(x+y)^0=1
	(x+y)^1=x+y;
	(x+y)^2=x^2+2xy+y^2
	(x+y)^3=x^3+3x^2y+3xy^2+y^3
	其实他们的系数就是杨辉三角
	而第n行x的系数分别为n~0
	第n行y的系数分别为0~n
	这个公式就叫——二项式定理 
	(x+y)^n=C(n,0)*x^n*y^0 + C(n,1)*x^(n-1)*y^1 + C(n,2)*x^(n-2)*y^2+...... + C(n,n)*x^0*y^n
			||				
			||				
			||				
	 n
	 ΣC(n,i)*x^(n-i)*y^i    计算i从0~n之间的时候,该多项式的和 
	i=0 

卢卡斯定理

/*	lucas定理
	转进制,不断除进制p,最后倒着连接余数
	例:25变为3进制:
			3|25............1
			  --- 
			  3|8...........2
			   ---
			    2
	所以25的3进制为221 
	
	n=25,m=12,p=3 
	25的三进制为221,12的三进制为110
	C(25,12)%p=C(2,1)*C(2,1)*C(1,0)%3
			  =2*2*1%3
			  =4%3
			  =1 
	C(n,m)%p= 
*/ 
cin>>n>>m>>p;
//n,m<=10^9 p<=100且为质数
for(int i=0;i<=p;i++){
	C[i][0]=1;
	for(int j=1;j<=i;j++)
		C[i][j]=(C[i-1][j-1]+C[i-1][j])%p
}
int lucas(int n,int m){//C(n,m)%p
	//O(log p) 
	while(n!=0){
		x[0]++;//p进制下共有多少位 
		x[x[0]]=n%p;//记录余数 
		n=n/p; 
	}
	while(m!=0){
		y[0]++; 
		y[y[0]]=m%p; 
		m=m/p; 
	}
	//x[1] x[2] ... x[x[0]] 就是n的p进制从低到高的表示
	//y[1] y[2] ... y[y[0]] 就是m的p进制从低到高的表示
	int ans=1;
	for(int i=1;i<=max(x[0],y[0]);i++)
		ans=1ll*ans*C[x[i]][y[i]]%p;
	return ans; 
}

组合数取模

//组合数取模 

//n,m <=1000
	for(int i=0;i<=n;i++){
		C[i][0]=1;
		for(int j=1;j<=i;j++)
			C[i][j]=(C[i-1][j-1]+C[i-1][j])%p;
	}

//n,m<=10^6 p是质数
	fac[0]=1;
	for(int i=1;i<=1000000;i++)
		fac[i]=1ll*fac[i-1]*i%p;
	C(n,m)=1ll
	fac[n]*ksm(fac[m],p-2,p)%p*ksm(fac[n-m],p-2,p)%p;

//n<=10^9 m<=10^3
	for(int i=1;i<=m;i++){
		fenmu[i]=i;
		fenzi[i]=n-i+1;
	} 
	for(int i=1;i<=m;i++)
		for(int j=1;j<=m;j++){
			int g=__gcd(fenzi[i],fenmu[j]);
			fenzi[i]/=g;
			fenmu[j]/=g;
		}
	int ans=1;
	for(int i=1;i<=m;i++)
		ans=1ll*ans*fenzi[i]%p;
		
//n,m<=10^9 p是<=100的质数 
//见(lucas) 

log

/*	log (a*b)=log a+logb
	log (a/b)=log a-logb
	log C(n,m)=log (n!/(m!(n-m)!))
			  =log n!-log m!+log (n-m)!
*/
	fac[0]=0;
	for(int i=1;i<=1000000;i++)
		fac[i]=fac[i-1]+log(i);
		//log i!=log(i-1)!+log(i)
	double logcnm(int n,int m){
		//log C(n,m)=log n!-log m!-log (n-m)!
		return fac[n]-fac[m]-fac[n-m];
	}
	C(n1,m1)<C(n2,m2)
	logcnm(n1,m2)<logcnm(n2,m2);

抽屉原理

/*
	抽屉原理: 
	基本:把n个东西放到n个抽屉里,一定有一个抽屉至少有2个东西
	拓展:把kn+1个东西放到n个抽屉里,一定有一个抽屉至少有k+1个东西
	
	Problem:
	给定n个抽屉,要求从中选出任意多个数,使得他们和为c的倍数 
	c<=n<=10^5

	洛谷P2218 
*/

容斥原理

/*
	容斥原理(n个圈的容斥原理) 
	 
	两个圈:
	A1学语文的人,A2学数学的人
	A1∩A2:同时学语文数学的人
	A1∪A2:学语文或者数学的人(总人数) 
	|A1∪A2|=|A1|+|A2|-|A1∩A2|
	∩:交集符号	∪:并集符号 
	
	
	三个圈;
	A1学语文的人,A2学数学的人,A3学英语的人
	A1∪A2∪A3:学语文或者数学或者英语的人(总人数)
	|A1∪A2∪A3|=|A1|+|A2|+|A3| -|A1∩A2|-|A1∩A3|-|A2∩A3| +|A1∩A2∩A3|
	
	四个圈 :
	A1学语文的人,A2学数学的人,A3学英语的人,A4学OI的人 
	A1∪A2∪A3∪A4:学语文或者数学或者英语或者OI的人(总人数)
	|A1∪A2∪A3∪A4|=|A1|+|A2|+|A3|+|A4|
					-|A1∩A2|-|A1∩A3|-|A1∩A4|-|A2∩A3|-|A2∩A4|-|A3∩A4| 
					+|A1∩A2∩A3|+|A1∩A2∩A4|+|A1∩A3∩A4|+|A2∩A3∩A4|
					-|A1∩A2∩A3∩A4|
		
	例题:有n对夫妻共2n个人,请问有多少种坐法?
	约束:1.一对夫妻坐着不能相邻
		  2.旋转相同算一种方案 
*/
posted @ 2024-02-29 04:47  CheZiHe929  阅读(13)  评论(0编辑  收藏  举报