重要知识点
1.最大公约数
ll gcd(ll x,ll y){
if (!x || !y) return x+y;
if (x%y==0) return y;
return gcd(y,x%y);
}
2.扩展欧几里得
int exgcd(int a,int b,int &x,int &y)
{
if (b==0)
{
x=1,y=0;
return a;
}
int q=exgcd(b,a%b,y,x);
y-=a/b*x;
return q;
}
3.逆元(mod为素数)
//O(n)求1~n的逆元
inv[1]=1;
for (int i=2;i<=n;++i)
inv[i]=(ll)(mod-mod/i)*inv[mod%i]%mod;
//求x的逆元
ll pow_mod(ll x, ll n)
{
ll res=1;
while(n>0)
{
if(n&1)res=res*x%mod;
x=x*x%mod;
n>>=1;
}
return res;
}
pow_mod(x,mod-2);
4.素数线性筛
v[1]=1;
for (int i=2;i<=N;i++)
{
if (!v[i])
p[++tot]=i;
for (int j=1;p[j]*i<=N;j++)
{
v[p[j]*i]=1;
if (!(i%p[j])) break;
}
}
5.欧拉函数
int m[maxn],phi[maxn],p[maxn],pt;
//m[i]是i的最小素因数,p是素数,pt是素数个数
void make()
{
phi[1]=1;
int N=maxn;
int k;
for(int i=2;i<N;i++)
{
if(!m[i])//i是素数
p[pt++]=m[i]=i,phi[i]=i-1;
for(int j=0;j<pt&&(k=p[j]*i)<N;j++)
{
m[k]=p[j];
if(m[i]==p[j])//为了保证以后的数不被再筛,要break
{
phi[k]=phi[i]*p[j];
/*这里的phi[k]与phi[i]后面的∏(p[i]-1)/p[i]都一样(m[i]==p[j])只差一个p[j],就可以保证∏(p[i]-1)/p[i]前面也一样了*/
break;
}
else
phi[k]=phi[i]*(p[j]-1);
//积性函数性质,f(i*k)=f(i)*f(k)
}
}
//本人写法
for (int i=2;i<N;i++){
if (!v[i]) p[++tot]=i,phi[i]=i-1;
for (int j=1;j<=tot && i*p[j]<N;j++){
v[i*p[j]]=1;
if (i%p[j]==0){
phi[i*p[j]]=phi[i]*p[j];
break;
} else phi[i*p[j]]=phi[i]*(p[j]-1);
}
}
}//其实就是在素数线性筛上动动手脚就好了~
6.排列组合(预处理)
void init(){
c[0][0]=c[1][0]=c[1][1]=1;
for (int i=2;i<=1000;i++){
c[i][0]=1;
for (int j=1;j<=i;j++)
c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
}
}
7.莫比乌斯反演
8.高斯消元
9.线性基
10.第一类斯特林数
s(p,k)的一个的组合学解释是:将p个物体排成k个非空循环排列的方法数。
s(p,k)的递推公式:
s(p,k)=(p−1)∗s(p−1,k)+s(p−1,k−1),1≥k≥p−1
边界条件:
S(p,p)=1,p≥0
S(p,0)=0,p≥1
递推关系的说明:
考虑第p个物品,p可以单独构成一个非空循环排列,这样前p-1种物品构成k-1个非空循环排列,方法数为s(p-1,k-1);
也可以前p-1种物品构成k个非空循环排列,而第p个物品插入第i个物品的左边,这有(p-1)*s(p-1,k)种方法。
for (int i=0;i<N;i++){
s[i][0]=0;s[i][i]=1;
for (int j=1;j<i;j++)
s[i][j]=(s[i-1][j-1]+(i-1)*s[i-1][j]%mod)%mod;
}
11.第二类斯特林数
S(p,k)的一个组合学解释是:将p个物体划分成k个非空的不可辨别的(可以理解为盒子没有编号)集合的方法数。
k!S(p,k)是把p个人分进k间有差别(如:被标有房号)的房间(无空房)的方法数。
S(p,k)的递推公式是:
S(p,k)=k∗S(p−1,k)+S(p−1,k−1),1≤k≤p−1
边界条件:
S(p,p)=1,p≥0
S(p,0)=0,p≥1
递推关系的说明:
考虑第p个物品,p可以单独构成一个非空集合,此时前p-1个物品构成k-1个非空的不可辨别的集合,方法数为S(p-1,k-1);
也可以前p-1种物品构成k个非空的不可辨别的集合,第p个物品放入任意一个中,这样有k*S(p-1,k)种方法。
for(int i=1;i<N;i++){
s[i][i]=1;s[i][0]=0;
for(j=1;j<i;j++)
s[i][j]=(s[i-1][j-1]+j*s[i-1][j]%mod)%mod;
}
12.fft
struct cp
{
double x,y;
inline cp(double _x=0.0,double _y=0.0)
{
x=_x;
y=_y;
}
friend inline cp operator + (cp a,cp b)
{
return cp(a.x+b.x,a.y+b.y);
}
friend inline cp operator - (cp a,cp b)
{
return cp(a.x-b.x,a.y-b.y);
}
friend inline cp operator * (cp a,cp b)
{
return cp(a.x*b.x-a.y*b.y,a.x*b.y+a.y*b.x);
}
}a[N],b[N],c[N],t[N];
void fft(cp *a,int len,int kd)
{
//kd=1时将系数表达式转换为点值表达式(就是n个点描述一个多项式)
//kd=-1时将点值表达式转换成系数表达式
for (int i=0;i<len;i++) t[i]=a[rev[i]];
for (int i=0;i<len;i++) a[i]=t[i];
//低级写法,ntt的写法更优高级
for (int i=2;i<=len;i*=2)
{
cp wn(cos(2*pi/i),kd*sin(2*pi/i));//公比
for (int k=0;k<len;k+=i)
{
cp w(1,0);//螺旋因子
for (int j=0;j<i/2;j++)
{
cp x=a[k+j];//蝶形操作
cp y=a[k+j+i/2]*w;
a[k+j]=x+y;//前半部分
a[k+j+i/2]=x-y;//后半部分
w=w*wn;
}
}
}
if(kd==-1)///IDFT
for(int i=0;i<len;i++)
a[i].x=round(a[i].x/len);//4舍5入
}
//rev[]的预处理
for(m=n,n=1;n<=m<<1;n<<=1) l++;
for(int i=0;i<n;i++) rev[i]=(rev[i>>1]>>1)|((i&1)<<l);
13.ntt
void ntt(ll *a,int len,int f) {
for (int i=0;i<len;i++)
if (rev[i]>i) swap(a[i],a[rev[i]]);
for(int i=2;i<=len;i<<=1) {
ll wn=power(3,((mod-1)/i*f+mod-1)%(mod-1)),m=i>>1;
//3为mod的原根
for(int j=0;j<len;j+=i) {
ll w=1;
for(int k=0;k<m;k++,w=w*wn%mod) {
ll x=a[j+k],y=a[j+k+m]*w%mod;
a[j+k]=(x+y)%mod,a[j+k+m]=(x-y+mod)%mod;
}
}
}
if(f==-1) {
ll ni=power(len,mod-2);
for(int i=0;i<len;i++) a[i]=a[i]*ni%mod;
}
}
14.多项式求逆
void getinv(ll *a,ll *b,int len){
static ll t[N];
if (len==1){
b[0]=power(a[0],mod-2);
return;
}
getinv(a,b,len/2);
memcpy(t,a,sizeof(a[0])*len);
memset(t+len,0,sizeof(a[0])*len);
int m=len,l=-1,tn=len;
for (len=1;len<=m;len*=2)l++;
for (int i=0;i<len;i++)rev[i]=(rev[i>>1]>>1)|((i&1)<<l);
ntt(t,len,1),ntt(b,len,1);
for (int i=0;i<len;i++)
t[i]=b[i]*(2ll-t[i]*b[i]%mod+mod)%mod;
ntt(t,len,-1);
for (int i=0;i<(len<<1);i++)
b[i]=t[i];
memset(b+tn,0,sizeof(b[0])*tn);
len=tn;
}
getinv(b,invb,l);
15.多项式开根
void getsqrt(ll *a,ll *b,int len){
static ll t[N],invb[N];
if (len==1){
b[0]=1;
return;
}
getsqrt(a,b,len/2);
memset(invb,0,sizeof(a[0])*len);
getinv(b,invb,len);
memcpy(t,a,sizeof(a[0])*len);
memset(t+len,0,sizeof(a[0])*len);
int m=len,l=-1,tn=len;
for (len=1;len<=m;len*=2)l++;
for (int i=0;i<len;i++)rev[i]=(rev[i>>1]>>1)|((i&1)<<l);
ntt(t,len,1);ntt(b,len,1);ntt(invb,len,1);
for (int i=0;i<len;i++)
t[i]=inv2*(b[i]+invb[i]*t[i]%mod)%mod;
ntt(t,len,-1);
for (int i=0;i<(len>>1);i++)
b[i]=t[i];
memset(b+tn,0,sizeof(b[0])+tn);
len=tn;
}
多项式需注意的地方:
1.多项式求逆和开根是函数里的变量不要和外面的全局变量重名。
2.memset和memcpy是不要弄错了位数。
3.注意每次搞完之后要去掉哪些位(保留题目中要求的就好了)或是将后面的为往前加(求和余数)
16.中国剩余定理(CRT)
int CRT(int a[],int m[],int n)
{
int M = 1;
int ans = 0;
for(int i=1; i<=n; i++)
M *= m[i];
for(int i=1; i<=n; i++)
{
int x, y;
int Mi = M / m[i];
extend_Euclid(Mi, m[i], x, y); //扩展欧几里德
ans = (ans + Mi * x * a[i]) % M;
}
if(ans < 0) ans += M;
return ans;
}
17.卡特兰数
定义
设
hn
为多边形划分为一个个三角形的方案数(区域内不相交)。
h1=1
hn=∑n−1k=1hkhn−k=1n(2n−2n−1)=4n−2n−1hn−1
应用
18.类欧几里得
求
ll calc(ll a,ll b,ll c,ll n){
ll s=0,t;
if (a>=c) t=a/c,a-=c*t,s+=n*(n+1)/2*t;
if (b>=c) t=b/c,b-=c*t,s+=(n+1)*t;
if (a==0) return s+(n+1)*(b/c);
ll m=(a*n+b)/c;
s+=n*m;
return s+calc(c,c-b-1,a,m-1);
}
一堆题
一些定理
Miller−Rabin
费马小定理:
对于素数p和任意整数a,有 ap≡a(modp) (同余)。反过来,满足 ap≡a(modp) ,p也几乎一定是素数。
伪素数:
如果n是一个正整数,如果存在和n互素的正整数a满足 an−1≡1(modn) ,我们说n是基于a的伪素数。如果一个数是伪素数,那么它几乎肯定是素数。
Miller-Rabin测试:
不断选取不超过n-1的基b(s次),计算是否每次都有 bn−1≡1(modn) ,若每次都成立则n是素数,否则为合数。
二次探测定理:
如果p是奇素数,则 x2≡1(modp) 的解为 x = 1 || x = p - 1(mod p);
具体实现过程:
∵
an−1≡1(modn)
那么它几乎肯定是素数。
令
r=n−1
,则
r=2k∗p
此时若为质数必须满足:
(1)
ar≡1(modn)
令
m=ar
(2)若
m21≡1(modn)
则
m20=1||m20=n−1
(3)若
m22≡1(modn)
则
m21=1||m21=n−1
…….
…….
…….
…….若
m2k≡1(modn)
则
m2k−1=1||m2k−1=n−1
Pollard−rho
//hdu3864 AC code
#include <cstdio>
#include <cstring>
#include <cmath>
#include <ctime>
#include <iostream>
#include <algorithm>
using namespace std;
typedef __int64 LL;
const LL NUM=10;//运算次数,Miller_Rabin算法为概率运算,误判率为2^(-NUM);
LL t,f[100];
LL mul_mod(LL a,LL b,LL n)//求a*b%n,由于a和b太大,需要用进位乘法
{
a=a%n;
b=b%n;
LL s=0;
while(b)
{
if(b&1)
s=(s+a)%n;
a=(a<<1)%n;
b=b>>1;
}
return s;
}
LL pow_mod(LL a,LL b,LL n)//求a^b%n
{
a=a%n;
LL s=1;
while(b)
{
if(b&1)
s=mul_mod(s,a,n);
a=mul_mod(a,a,n);
b=b>>1;
}
return s;
}
bool check(LL a,LL n,LL r,LL s)
{
LL ans,p,i;
ans=pow_mod(a,r,n);
p=ans;
for(i=1;i<=s;i++)
{
ans=mul_mod(ans,ans,n);
if(ans==1&&p!=1&&p!=n-1)return true;
p=ans;
}
if(ans!=1)return true;
return false;
}
bool Miller_Rabin(LL n)//Miller_Rabin算法,判断n是否为素数
{
if(n<2)return false;
if(n==2)return true;
if(!(n&1))return false;
LL i,r,s,a;
r=n-1;s=0;
while(!(r&1)){r=r>>1;s++;}
for(i=0;i<NUM;i++)
{
a=rand()%(n-1)+1;
if(check(a,n,r,s))
return false;
}
return true;
}
LL gcd(LL a,LL b)
{
return b==0?a:gcd(b,a%b);
}
LL Pollard_rho(LL n,LL c)//Pollard_rho算法,找出n的因子
{
LL i=1,j,k=2,x,y,d,p;
x=rand()%n;
y=x;
while(true)
{
i++;
x=(mul_mod(x,x,n)+c)%n;
if(y==x)return n;
if(y>x)p=y-x;
else p=x-y;
d=gcd(p,n);
if(d!=1&&d!=n)return d;
if(i==k)
{
y=x;
k+=k;
}
}
}
void find(LL n)//找出n的所有因子
{
if(Miller_Rabin(n))
{
f[t++]=n;//保存所有因子
return;
}
LL p=n;
while(p>=n)p=Pollard_rho(p,rand()%(n-1)+1);//由于p必定为合数,所以通过多次求解必定能求得答案
find(p);
find(n/p);
}
int main()
{
srand(time(NULL));//随机数设定种子
LL n;
while(cin>>n)
{
if(n==1){cout<<"is not a D_num"<<endl;continue;}//特判
t=0;
find(n);
if(t!=2&&t!=3){cout<<"is not a D_num"<<endl;continue;}
sort(f,f+t);
if(t==2)
{
if(f[0]!=f[1])cout<<f[0]<<" "<<f[1]<<" "<<n<<endl;
else cout<<"is not a D_num"<<endl;
}
else//n是一个素数的三次方
{
if(f[0]==f[1]&&f[1]==f[2])cout<<f[0]<<" "<<f[0]*f[0]<<" "<<n<<endl;
else cout<<"is not a D_num"<<endl;
}
}
return 0;
}
Lucas定理
证明:orz
Bell数
Bell数的定义:第n个Bell数表示集合{1,2,3,…,n}的划分方案数,即:B[0] = 1;
每一个Bell数都是第二类Stirling数的和,即:
Bell数的指数生成函数是:
Bell数的同余性质
Bell数模素数p的周期为:
、
void Bell(int T[],int MOD)
{
B[0] = 1;
B[1] = 1;
T[0] = 1;
for(int i=2;i<N;i++)
{
T[i-1] = B[i-1];
for(int j=i-2;j>=0;j--)
T[j] = (T[j]+T[j+1])%MOD;
B[i] = T[0];
}
}
中国剩余定理(CRT)
设正整数两两互素,则同余方程组
有整数解。并且在模
M=m1∗m2∗...∗mk
下的解是唯一的,解为
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=100;
ll n,a[N],m[N],ans;
void extend(ll a,ll b,ll &x,ll &y){
if (b==0){x=1;y=0;return;}
extend(b,a%b,y,x);y-=(a/b)*x;
}
ll gao(){
ll tot=1;
for (int i=1;i<=n;i++)
tot*=m[i];
for (int i=1;i<=n;i++){
ll mii,y,mi=tot/m[i];
extend(mi,m[i],mii,y);
while (mii<0) mii+=m[i];
ans=(ans+a[i]*mi%tot*mii%tot)%tot;
}
return ans;
}
int main(){
scanf("%lld",&n);
for (int i=1;i<=n;i++)
scanf("%lld%lld",&a[i],&m[i]);
printf("%lld\n",gao());
}
若正整数两两不互素,则%%%orz吧~
orz
多项式求逆
现在的问题是求A的逆
(modxn)
即
A−1(x)
A(x)∗A−1(x)≡1(modxn)
显然但
n=1
时
A(x)≡c(modxn)
∴
A−1(x)≡c−1(modxn)
设
B(x)=A−1(x)
则
A(x)∗B(x)≡1(modxn)
设
A(x)∗B′(x)≡1(modxn2)
∵
A(x)∗B(x)≡1(modxn2)
∴
B(x)−B′(x)≡0(modxn2)
∴
B2(x)−2B(x)B′(x)+B′2(x)≡0(modxn)
∴
B(x)−2B′(x)+A(x)B′2(x)≡0(modxn)
∴
B(x)≡2B′(x)−A(x)B′2(x)(modxn)
也就是说我们可以分治一波,边界就是
n=1
Sierpinski三角形
或
#include<cstdio>
int main()
{
const int n=(1<<5)-1;
int i,j;
for (i=0; i<=n; i++)
{
for (j=0; j<=n; j++)
printf( (i&j)==j ? "#" : " ");/*☆☆☆☆☆*/
printf("\n");
}
getchar();
return 0;
}
输出:
#
##
# #
####
# #
## ##
# # # #
########
# #
## ##
# # # #
#### ####
# # # #
## ## ## ##
# # # # # # # #
################
# #
## ##
# # # #
#### ####
# # # #
## ## ## ##
# # # # # # # #
######## ########
# # # #
## ## ## ##
# # # # # # # #
#### #### #### ####
# # # # # # # #
## ## ## ## ## ## ## ##
# # # # # # # # # # # # # # # #
################################
奇怪思想
把水倒掉
将一个陌生的问题转换成熟悉的问题。
将一陀问题转化成两陀小问题再变成四陀小小问题….