数论是 OI 在数学知识板块最重要最基础的一块,它包含许多基础算法,下面分别讲解。
快速幂
快速幂取模
求
暴力算法时间复杂度为
有没有更快的算法呢?我们考虑把
带回
由于我们可以
inline long long power(long long a,long long b,long long p)
{
long long ans=1;
while(b)
{
if(b&1)
ans=ans*a%p;
a=a*a%p;
b>>=1;
}
return ans%p;
}
等比数列求和
已知等比数列首项为
这题有很多种方法,首先我们发现等比数列是个递推式,我们可以用矩阵加速递推来做。
其次假如
把
但当
我们把式子列出来:
我们令
我们对
如果
这样同样可以在
long long solve(long long a1,long long n,long long q)
{
if(n==1)
return a1;
if(n&1)
return (solve(a1,n-1,q)%p+a1*power(q,n-1,p))%p;
else
return (solve(a1,n/2,q)%p*(1+power(q,n/2,p))%p)%p;
}
long long sumn(long long a1,long long n,long long q)
{
return a1+solve(a1,n-1,q);
}
发现在 solve 函数中,快速幂的指数和 solve 的 solve 的返回值改成 pair,即同时记录
pair solve(long long a1,long long n,long long q)
{
if(n==1)
return make_pair(a1,q);
if(n&1)
{
pair<int,int> p=solve(a1,n-1,q);
return make_pair(p.first%p+a1*p.second%p,p.second*q%p);
}
else
{
pair<int,int> p=solve(a1,n/2,q);
return make_pair(p.first%p*(1+p.second)%p,p.second*p.second%p);
}
}
long long sumn(long long a1,long long n,long long q)
{
return a1+solve(a1,n-1,q).first;
}
矩阵快速幂
求矩阵快速幂。
关于矩阵知识请见线性代数总结。跟快速幂相似,把过程中的乘法换成矩阵乘法即可。
matrix multi(matrix a,matrix b)
{
matrix c;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
c.a[i][j]=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
for(int k=1;k<=n;k++)
c.a[i][j]=(c.a[i][j]+a.a[i][k]*b.a[k][j]%mod)%mod;
return c;
}
matrix power(matrix a,long long b)
{
matrix ans=a;
b--;
for(;b;b>>=1)
{
if(b&1)
ans=multi(ans,a);
a=multi(a,a);
}
return ans;
}
质数
质数的定义很简单,即大于
质数判定
判断一个数是否是质数。
暴力做法
时间复杂度为
bool isprime(int n)
{
if(n<2)
return false;
for(int i=2;i*i<=n;i++)
if(n%i==0)
return false;
return true;
}
若已提前将
bool isprime(int n)
{
if(n<2)
return false;
for(int i=1;i<=cnt;i++)
if(n%prime[i]==0)
return false;
return true;
}
Miller Rabin 算法
HDU2138 How many prime numbers
时间复杂度为
Miller Rabin 并不是一个确定性算法,它有一定的错误率,但能极大概率测试其是否为素数。
要学习 Miller Rabin 算法,首先要了解两个基础定理。
费马小定理
当
它的另一个形式是对于任意整数
二次探测定理
当
理解这两个定理后,就可以开始判断质数了。首先对于偶数和 int 范围内的数不会出错。
最后需要注意,由于要求 long long 类型的平方,可能会溢出,因此需要快速乘来进行乘法。
long long p[9]={0,2,3,7,61,24251};
inline long long multi(long long a,long long b,long long p)
{
long long ans=0;
for(;b;b>>=1)
{
if(b&1)
ans=(ans+a)%p;
a=(a+a)%p;
}
return ans%p;
}
inline long long power(long long a,long long b,long long p)
{
long long ans=1%p;
for(;b;b>>=1)
{
if(b&1)
ans=multi(ans,a,p);
a=multi(a,a,p);
}
return ans%p;
}
inline bool Miller_Rabin(long long x)
{
if(x==2)
return true;
if(x<2||(!(x&1))||x==46856248255981)//特判
return false;
long long s=0,t=x-1;
while(!(t&1))
{
t>>=1;
s++;
}//拆分s,t
for(int i=1;i<=5&&p[i]<x;i++)
{
long long r=power(p[i],t,x);//判断每个质数
for(int j=1;j<=s;j++)//平方s次
{
long long q=multi(r,r,x);
if(q==1&&r!=1&&r!=x-1)//二次探测定理
return false;
r=q;
}
if(r!=1)//费马小定理
return false;
}
return true;
}
筛法
筛法的作用其实是将 虽然有点大材小用)。
质数筛主要有两种:埃氏筛和线性筛(欧拉筛)
Eratosthenes 筛法
简称埃氏筛。时间复杂度为
for(int i=2;i<=n;i++)
{
if(!vis[i])
{
prime[++cnt]=i;
for(int j=i*2;j<=n;j+=i)
vis[j]=1;
}
}
Euler 筛法
中文名为欧拉筛,也称为线性筛。时间复杂度为
for(int i=2;i<=n;i++)
{
if(!vis[i])
prime[++cnt]=i;
for(int j=1;j<=cnt;j++)
{
if(i*prime[j]>n)
break;
vis[i*prime[j]]=1;
if(i%prime[j]==0)//这一步保证每个合数只会被标记一次
break;
}
}
分解质因数
将一个数
暴力做法
时间复杂度为
void divide(int n)
{
int q=sqrt(n);
for(int i=2;i<=q;i++)
if(n%i==0)
{
prime[++cnt]=i;
while(n%i==0)
{
n/=i;
c[cnt]++;
}
}
if(n>1)
{
prime[++cnt]=n;
c[cnt]=1;
}
}
同理,若已经将
void divide(int n)
{
tot=0;
for(int i=1;i<=cnt;i++)
if(n%prime[i]==0)
{
p[++tot]=prime[i];
while(n%prime[i]==0)
{
n/=prime[i];
c[tot]++;
}
}
if(n>1)
{
p[++tot]=n;
c[tot]=1;
}
}
Pollard Rho 算法
Pollard Rho 是一个随机算法,能快速找到一个数
我们尝试随机在
生日悖论
假如说一个班上有
生日悖论给了我们启示:当我们随机选择组合时,满足答案的组合会比满足答案的单体要多得多,因此可以用来提高正确率。 例如在
我们回到原问题,我们同理选择
Pollard Rho 算法只用到了两个数
我们令 rand 随机生成开始,令
但是这个数列也会进入死循环,因为它的生成数列的轨迹很香希腊字母
该怎么判断这个环呢?这里有几个不同的方法。
首先就是暴力,拿个数组记录之前所有产生的数判断即可。
另一种方法是 Floyd 发明的判环算法,也被称为“龟兔赛跑”算法。假设我们在一个圆形轨道上行走,我们如何直到我们已经走完了一圈呢?我们设两个人,暂且叫它们 mich 和 clockwhite,我们让 clockwhite 按照 mich 的两倍速度从同一起点往前走,这是一个典型的追及问题,当 clockwhite 第一次追上 mich 时,我们就知道 clockwhite 已经走了至少一圈了。在本问题中我们就设
inline long long f(long long x,long long a,long long n)
{
return (__int128)(x*x+a)%n;
}
inline long long floyd(long long n)
{
long long a=rand()%(n-1)+1;
int s=f(2,a,n);
int t=f(s,a,n);
while(s!=t)
{
long long d=gcd(abs(t-s),n);
if(d>1)
return d;//找到约数
s=f(s,a,n);
t=f(f(t,a,n),a,n);
}
return -1;//没找到,重新调整参数a
}
第三种方法是基于路径倍增,同时还有一些常数优化。由于求
最后我们结合 Miller Rabin 算法,先判断这个数是否为质数,若不是则用 Pollard Rho 找因子
以下是基于路径倍增的代码。
/*
* @Author: clorf
* @Date: 2020-09-21 10:19:30
* @Last Modified by: clorf
* @Last Modified time: 2020-09-21 11:51:39
*/
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<ctime>
#include<cctype>
#define INF 1e9
using namespace std;
const int maxn=100010;
const int mod=1e9+7;
const double Pi=acos(-1.0);
const double eps=1e-7;
//1.数组开小
//2.没用long long/爆了类型存储范围
//3.写之前式子没推清楚
//4.变量写错(想当然typo/没想清楚含义)
//5.写之前没想清楚复杂度
template<class T>void read(T &x)
{
x=0;int f=0;char ch=getchar();
while(!isdigit(ch)) {f|=(ch=='-');ch=getchar();}
while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
x=f?-x:x;
return;
}
int t;
long long n,pr[6]={0,2,3,7,61,24251},maxx;
inline long long power(long long a,long long b,long long p)
{
long long ans=1;
for(;b;b>>=1)
{
if(b&1)
ans=(__int128)ans*a%p;
a=(__int128)a*a%p;
}
return ans%p;
}
inline bool Miller_Rabin(long long x)
{
if(x==2)
return true;
if(x<2||x==46856248255981||(!(x&1)))
return false;
long long k=x-1,s=0;
while(!(k&1))
{
k>>=1;
s++;
}
for(int i=1;i<=5&&pr[i]<x;i++)
{
long long r=power(pr[i],k,x);
for(int j=1;j<=s;j++)
{
long long q=(__int128)r*r%x;
if(q==1&&r!=1&&r!=x-1)
return false;
r=q;
}
if(r!=1)
return false;
}
return true;
}
long long gcd(long long a,long long b)
{
if(!b)
return a;
return gcd(b,a%b);
}
inline long long f(long long x,long long c,long long p)
{
return ((__int128)x*x+c)%p;
}
inline long long Pollard_Rho(long long x)
{
long long s=0,t=0;
long long c=1ll*rand()%(x-1)+1,val=1;
for(int k=1;;k<<=1,s=t,val=1)
{
for(int step=1;step<=k;step++)
{
t=f(t,c,x);
val=(__int128)val*abs(t-s)%x;
if((step%127)==0)
{
long long d=gcd(val,x);
if(d>1)
return d;
}
}
long long d=gcd(val,x);
if(d>1)
return d;
}
}
void solve(long long x)
{
if(x<=maxx||x<2)
return ;
if(Miller_Rabin(x))
{
maxx=max(maxx,x);
return ;
}
long long p=x;
while(p>=x)//while防止PR(x)=x
p=Pollard_Rho(x);
while(!(x%p))
x/=p;
solve(p);
solve(x);
}
int main()
{
scanf("%d",&t);
while(t--)
{
srand((unsigned)time(NULL));
scanf("%lld",&n);
maxx=0;
solve(n);
if(maxx==n)
printf("Prime\n");
else
printf("%lld\n",maxx);
}
return 0;
}
阶乘质因数分解
对
若我们采用暴力做法,时间复杂度为
我们考虑更优秀的算法。首先用线性筛筛出
为什么?首先
时间复杂度小于
/*
* @Author: clorf
* @Date: 2020-09-21 14:35:35
* @Last Modified by: clorf
* @Last Modified time: 2020-09-21 16:07:17
*/
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<ctime>
#include<cctype>
#define INF 1e9
using namespace std;
const int maxn=10010;
const int mod=1e9+7;
const double Pi=acos(-1.0);
const double eps=1e-7;
//1.数组开小
//2.没用long long/爆了类型存储范围
//3.写之前式子没推清楚
//4.变量写错(想当然typo/没想清楚含义)
//5.写之前没想清楚复杂度
template<class T>void read(T &x)
{
x=0;int f=0;char ch=getchar();
while(!isdigit(ch)) {f|=(ch=='-');ch=getchar();}
while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
x=f?-x:x;
return;
}
int n,pr[maxn],cnt,c[maxn];
bool vis[maxn];
int main()
{
scanf("%d",&n);
for(int i=2;i<=n;i++)
{
if(!vis[i])
{
pr[++cnt]=i;
vis[i]=1;
}
for(int j=1;j<=cnt;j++)
{
if(i*pr[j]>n)
break;
vis[i*pr[j]]=1;
if(i%pr[j]==0)
break;
}
}
for(int i=1;i<=cnt;i++)
{
int t=n;
while(t)
{
t/=pr[i];
c[i]+=t;
}
}
for(int i=1;i<=cnt;i++)
printf("%d %d\n",pr[i],c[i]);
return 0;
}
欧拉函数
欧拉函数是基础数论函数之一,用
定义
求一个数的欧拉函数值
直接根据定义质因数分解求即可,可以用 Pollard Rho 算法优化。
inline long long eular(long long x)
{
long long ans=x;
long long p=x;
for(int i=2;i*i<=x;i++)
if(p%i==0)
{
ans=ans/i*(i-1);
while(p%i==0)
p/=i;
}
if(p>1)
ans=ans/p*(p-1);
return ans;
}
求多个数的欧拉函数值
求
inline void eular(long long n)
{
for(int i=2;i<=n;i++)
phi[i]=i;
for(int i=2;i<=n;i++)
if(phi[i]==i)
for(int j=i;j<=n;j+=i)
phi[j]=phi[j]/i*(i-1);
}
利用线性筛和下面的最后一个性质,可以在
inline void eular(long long n)
{
for(int i=2;i<=n;i++)
{
if(!vis[i])
{
vis[i]=1;
pr[++cnt]=i;
phi[i]=i-1;
}
for(int j=1;j<=cnt;j++)
{
if(pr[j]*i>n)
break;
vis[i*pr[j]]=pr[j];
phi[i*pr[j]]=phi[i]*(i%pr[j]?pr[j]-1:pr[j]);
if(i%pr[j]==0)
break;
}
}
}
性质
-
当
是质数时, ; -
是积性函数,即 ,那么 ; -
; -
若
, 是质数,那么 ; -
, 中与 互质数的和为 ; -
若
是质数且 ,若 ,则 ;若 ,则 。
欧拉定理
若
扩展欧拉定理
扩展欧拉定理的主要作用是用于降幂。
其中当
/*
* @Author: clorf
* @Date: 2020-09-21 16:17:55
* @Last Modified by: clorf
* @Last Modified time: 2020-09-21 16:33:56
*/
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<ctime>
#include<cctype>
#define INF 1e9
using namespace std;
const int maxn=20000010;
const double Pi=acos(-1.0);
const double eps=1e-7;
//1.数组开小
//2.没用long long/爆了类型存储范围
//3.写之前式子没推清楚
//4.变量写错(想当然typo/没想清楚含义)
//5.写之前没想清楚复杂度
template<class T>void read(T &x)
{
x=0;int f=0;char ch=getchar();
while(!isdigit(ch)) {f|=(ch=='-');ch=getchar();}
while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
x=f?-x:x;
return;
}
long long a,b,m,t,mod;
char s[maxn];
inline long long eular(long long x)
{
long long ans=x;
long long p=x;
for(int i=2;i*i<=x;i++)
if(p%i==0)
{
ans=ans/i*(i-1);
while(p%i==0)
p/=i;
}
if(p>1)
ans=ans/p*(p-1);
return ans;
}
inline long long power(long long a,long long b,long long p)
{
if(b<=0)
return 1;
long long ans=1;
for(;b;b>>=1)
{
if(b&1)
ans=ans*a%p;
a=a*a%p;
}
return ans%p;
}
int main()
{
scanf("%lld%lld",&a,&m);
mod=eular(m);
bool flag=0;
scanf("%s",s+1);
int len=strlen(s+1);
for(int i=1;i<=len;i++)
{
b=10*b+s[i]-'0';
if(b>=mod)
{
b%=mod;
flag=1;
}
}
if(!flag)
printf("%lld",power(a,b,m));
else
printf("%lld",power(a,b%mod+mod,m));
return 0;
}
最大公约数
最大公约数即
求最大公约数
欧几里得算法
已知两个数
欧几里得算法可以在
然后就可以递归处理了。
int gcd(int a,int b)
{
if(!b)
return a;
return gcd(b,a%b);
}
更相减损法
其实不太常用。出自《九章算术》。式子如下:
int gcd(int a,int b)
{
if(a==b)
return a;
else if(a>b)
a-=b;
else
b-=a;
return gcd(a,b);
}
二进制算法
避免了欧几里得算法的大量取模操作,可以有效减少时间消耗。
对于两个数
- 若
都为偶数,那么 ; - 若
为偶数, 为奇数,那么 ; - 若
为奇数, 为偶数,那么 ; - 若
都为奇数,那么 。
递归即可。
int gcd(int a,int b)
{
if(a<b)
return gcd(b,a);
if((a&1)&&(b&1))
return gcd(b,a-b);
else if((a&1)&&(!(b&1)))
return gcd(a,b/2);
else if((!(a&1))&&(b&1))
return gcd(a/2,b);
else
return gcd(a/2,b/2)*2;
}
最小公倍数
最小公倍数即
求最小公倍数先要求最大公约数,因为满足这样一个定理:
于是求出
inline int lcm(int a,int b)
{
return a/gcd(a,b)*b;
}
裴蜀定理
又称贝祖定理(
扩展到多个数的情况,对于
/*
* @Author: clorf
* @Date: 2020-09-21 19:47:13
* @Last Modified by: clorf
* @Last Modified time: 2020-09-21 20:00:56
*/
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<ctime>
#include<cctype>
#define INF 1e9
using namespace std;
const int maxn=100010;
const int mod=1e9+7;
const double Pi=acos(-1.0);
const double eps=1e-7;
//1.数组开小
//2.没用long long/爆了类型存储范围
//3.写之前式子没推清楚
//4.变量写错(想当然typo/没想清楚含义)
//5.写之前没想清楚复杂度
template<class T>void read(T &x)
{
x=0;int f=0;char ch=getchar();
while(!isdigit(ch)) {f|=(ch=='-');ch=getchar();}
while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
x=f?-x:x;
return;
}
int n,ans;
int gcd(int a,int b)
{
if(!b)
return a;
return gcd(b,a%b);
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
int x;
scanf("%d",&x);
ans=gcd(ans,abs(x));
}
printf("%d",ans);
return 0;
}
一道需要运用裴蜀定理的题,就是要从 map 存储。
/*
* @Author: clorf
* @Date: 2020-09-29 10:00:29
* @Last Modified by: clorf
* @Last Modified time: 2020-09-29 10:11:33
*/
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<ctime>
#include<cctype>
#include<unordered_map>
#define INF 1e9
using namespace std;
const int maxn=1010,maxv=10000010;
const double Pi=acos(-1.0);
const double eps=1e-7;
//1.数组开小
//2.没用long long/爆了类型存储范围
//3.写之前式子没推清楚
//4.变量写错(想当然typo/没想清楚含义)
//5.写之前没想清楚复杂度
template<class T>void read(T &x)
{
x=0;int f=0;char ch=getchar();
while(!isdigit(ch)) {f|=(ch=='-');ch=getchar();}
while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
x=f?-x:x;
return;
}
long long n,k;
long long v[maxn],cnt,ans;
unordered_map<int, long long> fac, p;
int main()
{
scanf("%lld%lld",&n,&k);
for(int i=1;i<=n;i++)
scanf("%lld",&v[i]);
for(int i=1;i<=n;i++)
for(int j=1;j*j<=v[i];j++)
if(v[i]%j==0)
{
fac[++cnt]=j;
p[j]++;
int r=v[i]/j;
if(j!=r)
{
fac[++cnt]=r;
p[r]++;
}
}
for(int i=1;i<=cnt;i++)
if(p[fac[i]]>=k)
ans=max(ans,fac[i]);
printf("%lld",ans);
return 0;
}
扩展欧几里得
扩展欧几里得算法用于求
因为
于是可以得到
于是可以递归求解。边界条件是
inline int exgcd(int a,int b,int &x,int &y)
{
if(!b)
{
x=1;
y=0;
return a;
}
int ans=exgcd(b,a%b,x,y);
int t=x;
x=y;
y=t-a/b*y;
return ans;
}
得到了一组特解
然后我们就可以解普通的二元一次方程
首先如果 exgcd 求出的
最小正整数解
同时,当
/*
* @Author: clorf
* @Date: 2020-09-21 20:01:22
* @Last Modified by: clorf
* @Last Modified time: 2020-09-21 22:49:50
*/
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<ctime>
#include<cctype>
#define INF 1e9
using namespace std;
const int maxn=100010;
const int mod=1e9+7;
const double Pi=acos(-1.0);
const double eps=1e-7;
//1.数组开小
//2.没用long long/爆了类型存储范围
//3.写之前式子没推清楚
//4.变量写错(想当然typo/没想清楚含义)
//5.写之前没想清楚复杂度
template<class T>void read(T &x)
{
x=0;int f=0;char ch=getchar();
while(!isdigit(ch)) {f|=(ch=='-');ch=getchar();}
while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
x=f?-x:x;
return;
}
long long t,a,b,c;
long long exgcd(long long a,long long b,long long &x,long long &y)
{
if(!b)
{
x=1;
y=0;
return a;
}
long long ans=exgcd(b,a%b,x,y);
long long t=x;
x=y;
y=t-a/b*y;
return ans;
}
int main()
{
scanf("%lld",&t);
while(t--)
{
long long x,y;
scanf("%lld%lld%lld",&a,&b,&c);
long long num=exgcd(a,b,x,y);
if(c%num!=0)
{
printf("-1\n");
continue;
}
long long k=c/num;
long long x0=x*k,y0=y*k;
long long p=a/num,q=b/num;
long long miny=(y0%p+p)%p;
long long minx=(x0%q+q)%q;
if(!minx)
minx+=q;
if(!miny)
miny+=p;
long long maxy=(c-a*minx)/b;
long long maxx=(c-b*miny)/a;
if(maxy<=0)
printf("%lld %lld\n",minx,miny);
else
{
long long num1=(maxy-miny)/p+1;
long long num2=(maxx-minx)/q+1;
long long ans=min(num1,num2);
printf("%lld %lld %lld %lld %lld\n",ans,minx,miny,maxx,maxy);
}
}
return 0;
}
类欧几里得
给定
首先令:
然后我们分情况开始推:
对于
对于
对于
对于
对于
对于
对于
对于
对于
对于
对于
对于
对于
对于
对于
由此我们得到了
/*
* @Author: clorf
* @Date: 2020-09-22 09:18:16
* @Last Modified by: clorf
* @Last Modified time: 2020-09-22 10:26:48
*/
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<ctime>
#include<cctype>
#define INF 1e9
using namespace std;
const int maxn=100010;
const int mod=998244353;
const double Pi=acos(-1.0);
const double eps=1e-7;
//1.数组开小
//2.没用long long/爆了类型存储范围
//3.写之前式子没推清楚
//4.变量写错(想当然typo/没想清楚含义)
//5.写之前没想清楚复杂度
template<class T>void read(T &x)
{
x=0;int f=0;char ch=getchar();
while(!isdigit(ch)) {f|=(ch=='-');ch=getchar();}
while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
x=f?-x:x;
return;
}
struct euclid
{
long long f;
long long h;
long long g;
};
long long t;
const long long inv2=499122177ll;
const long long inv6=166374059ll;
inline long long sum1(long long x)
{
return x*(x+1)/2%mod;
}
inline long long sum2(long long x)
{
return x*(x+1)%mod*(x+x+1)%mod*inv6%mod;
}
euclid solve(long long a,long long b,long long c,long long n)
{
// printf("%lld %lld %lld %lld\n",a,b,c,n);
long long x=0,y=0,z=0;
euclid ans;
ans.f=ans.g=ans.h=0;
if(!n)
{
long long q=b/c;
q%=mod;
x=q;
y=q*q%mod;
ans=(euclid){x%mod,y%mod,0};
return ans;
}
if(!a)
{
long long u=sum1(n);
long long q=b/c;
q%=mod;
x=(n+1)*q%mod;
y=(n+1)*q%mod*q%mod;
z=q*u%mod;
ans=(euclid){x%mod,y%mod,z%mod};
return ans;
}
if((a>=c)||(b>=c))
{
long long r=a/c;
r%=mod;
long long q=b/c;
q%=mod;
euclid p=solve(a%c,b%c,c,n);
long long u=sum1(n);
long long v=sum2(n);
x=r*u%mod;
x=(x+(n+1)*q%mod)%mod;
x=(x+p.f)%mod;
y=r*r%mod*v%mod;
y=(y+(n+1)*q%mod*q%mod)%mod;
y=(y+p.h)%mod;
y=(y+2*r%mod*q%mod*u%mod)%mod;
y=(y+2*q%mod*p.f)%mod;
y=(y+2*r%mod*p.g)%mod;
z=r*v%mod;
z=(z+q*u%mod)%mod;
z=(z+p.g)%mod;
ans=(euclid){x%mod,y%mod,z%mod};
return ans;
}
long long m=(a*n+b)/c-1;
euclid p=solve(c,c-b-1,a,m);
x=n*(m+1)%mod;
x=(x-p.f+mod)%mod;
y=n*(m+1)%mod*(m+1)%mod;
long long j=(2*p.g%mod+p.f)%mod;
y=(y-j+mod)%mod;
z=n*(n+1)%mod*(m+1)%mod;
long long i=(p.h+p.f)%mod;
z=(z-i+mod)%mod;
z=z*inv2%mod;
ans=(euclid){x%mod,y%mod,z%mod};
return ans;
}
int main()
{
scanf("%lld",&t);
while(t--)
{
long long n,a,b,c;
scanf("%lld%lld%lld%lld",&n,&a,&b,&c);
euclid ans=solve(a,b,c,n);
printf("%lld %lld %lld\n",ans.f%mod,ans.h%mod,ans.g%mod);
}
return 0;
}
给定分数
我们的难点就是在于把小数转成
首先当
然后分类讨论,当
/*
* @Author: clorf
* @Date: 2020-09-27 15:54:05
* @Last Modified by: clorf
* @Last Modified time: 2020-09-27 16:16:16
*/
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<ctime>
#include<cctype>
#define INF 1e9
using namespace std;
const int maxn=100010;
const double Pi=acos(-1.0);
const double eps=1e-7;
//1.数组开小
//2.没用long long/爆了类型存储范围
//3.写之前式子没推清楚
//4.变量写错(想当然typo/没想清楚含义)
//5.写之前没想清楚复杂度
inline void read(long long &x)
{
x=0;char ch;
while((ch=getchar())^'.');
while(isdigit(ch=getchar())){x=(x<<1)+(x<<3)+(ch&15);}
return;
}
int n;
long long a,b,c,d,x,y,p;
long long v;
long long gcd(long long a,long long b)
{
if(!b)
return a;
return gcd(b,a%b);
}
void solve(long long a,long long b,long long c,long long d,long long &x,long long &y,bool type)//求a/b和c/d中间的分数x/y
{
long long minn=type?a/b+1:(a-1)/b+1;
long long maxx=type?c/d:(c-1)/d;
if(minn<=maxx)
{
x=minn;
y=1;
return ;
}
if(!a)
{
x=1;
y=d/c+1;
return ;
}
if(a<b)
{
solve(d,c,b,a,x,y,type^1);
swap(x,y);
}
else
{
solve(a%b,b,c-(a/b)*d,d,x,y,type);
x+=y*(a/b);
}
}
int main()
{
while(scanf("%d",&n)!=EOF)
{
read(v);
p=1;
for(int i=1;i<=n+1;i++)
p*=10;
a=max(v*10-5,0ll);
b=p;
long long k=gcd(a,b);
a/=k;
b/=k;
c=v*10+5;
d=p;
k=gcd(c,d);
c/=k;
d/=k;
solve(a,b,c,d,x,y,0);//type=0左边取等右边不取等,=1右边取等左边不取等
printf("%lld\n",y);
}
return 0;
}
线性同余方程
形如
乘法逆元
对于一个线性同余方程
求解乘法逆元的方法很多,下面一一讲解。
扩展欧几里得法
用扩展欧几里得来求解乘法逆元。我们可以把同余方程展开,可以得到
inline int exgcd(int a,int b,int &x,int &y)
{
if(!b)
{
x=1;
y=0;
return a;
}
int ans=exgcd(b,a%b,x,y);
int t=x;
x=y;
y=t-a/b*y;
return ans;
}
inline int inv(int a,int p)
{
int x,y;
x=y=0;
exgcd(a,p,x,y);
x=(x%p+p)%p;
return x;
}
费马小定理法
只适用于
所以此时
inline long long power(long long a,long long b,long long p)
{
long long ans=1;
while(b)
{
if(b&1)
ans=ans*a%p;
a=a*a%p;
b>>=1;
}
return ans%p;
}
inline long long inv(long long a,long long p)
{
return power(a,p-2,p);
}
线性求逆元
求
我们需要在
于是我们就得到了
inv[1]=1;
for(int i=2;i<=n;i++)
inv[i]=(p-p/i)*inv[p%i]%p;
求任意
我们首先计算
然后我们可以算出所有的
s[0]=0;
for(int i=1;i<=n;i++)
s[i]=s[i-1]*a[i]%p;
invs[n]=power(invs[n],p-2,p);
for(int i=n;i>=1;i--)
invs[i-1]=invs[i]*a[i]%p;
for(int i=1;i<=n;i++)
inv[i]=invs[i]*s[i-1]%p;
求
推导方式和上面大同小异。先将
fac[0]=1;
for(int i=1;i<=n;i++)
fac[i]=fac[i-1]*i%p;
inv[n]=power(fac[n],p-2,p);
for(int i=n-1;i>=1;i--)
inv[i]=inv[i+1]*(i+1)%p;
/*
* @Author: clorf
* @Date: 2020-09-21 16:08:35
* @Last Modified by: clorf
* @Last Modified time: 2020-09-21 16:13:20
*/
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<ctime>
#include<cctype>
#define INF 1e9
using namespace std;
const int maxn=3000010;
const int mod=1e9+7;
const double Pi=acos(-1.0);
const double eps=1e-7;
//1.数组开小
//2.没用long long/爆了类型存储范围
//3.写之前式子没推清楚
//4.变量写错(想当然typo/没想清楚含义)
//5.写之前没想清楚复杂度
template<class T>void read(T &x)
{
x=0;int f=0;char ch=getchar();
while(!isdigit(ch)) {f|=(ch=='-');ch=getchar();}
while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
x=f?-x:x;
return;
}
long long n,p,inv[maxn];
int main()
{
scanf("%lld%lld",&n,&p);
inv[0]=inv[1]=1;
for(int i=2;i<=n;i++)
inv[i]=inv[p%i]*(p-p/i)%p;
for(int i=1;i<=n;i++)
printf("%lld\n",inv[i]);
return 0;
}
这道题时间卡的很紧,但是主要有两种解法。
首先容易想到通分求解。处理出
#include<cstdio>
#include<cctype>
#define INF 1e9
using namespace std;
const int maxn=5e6+1;
inline void read(int &x)
{
x=0;char ch=getchar();
while(!isdigit(ch)) {ch=getchar();}
while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return;
}
int n,p,k,a[maxn];
int M=1,l[maxn]={1},r[maxn],b[maxn]={1},ans;
int power(int a,int b,int p)
{
int ans=1;
while(b)
{
if(b&1)
ans=(long long)ans*a%p;
a=(long long)a*a%p;
b>>=1;
}
return ans;
}
int main()
{
read(n),read(p),read(k);
for(register int i=1;i<=n;++i)
{
read(a[i]);
l[i]=(long long)l[i-1]*a[i]%p;
b[i]=(long long)b[i-1]*k%p;
}
r[n+1]=1;
for(register int i=n;i;--i)
{
r[i]=(long long)r[i+1]*a[i]%p;
ans=(ans+(long long)b[i]*l[i-1]%p*r[i+1])%p;
}
printf("%d",(long long)ans*power(l[n],p-2,p)%p);
return 0;
}
第二种方法则比较奇妙,数组甚至都不用开。我们就对每一个新的项依次通分即可,总共需要通分
/*
* @Author: clorf
* @Date: 2020-09-15 15:10:20
* @Last Modified by: clorf
* @Last Modified time: 2020-09-15 16:22:49
*/
#include<cstdio>
#include<cctype>
using namespace std;
const int maxn=5000001;
inline void read(int &x)
{
x=0;char ch=getchar();
while(!isdigit(ch)) {ch=getchar();}
while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return;
}
int n,p,k,a[maxn],ans,num=1,M=1,x;
inline int inv(int x)
{
return ~-x?(long long)(p-p/x)*inv(p%x)%p:1;
}
int main()
{
read(n),read(p),read(k);
for(register int i=1;i<=n;++i)
{
read(x);
num=(long long)num*k%p;
ans=((long long)ans*x+(long long)M*num)%p;
M=(long long)M*x%p;
}
printf("%d",(long long)ans*inv(M)%p);
return 0;
}
解线性同余方程
具体方法和扩展欧几里得法求逆元一样。把它用带余除法转成二元一次方程求解即可。
#include<cstdio>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<algorithm>
using namespace std;
long long a,b,x,y,num;
long long exgcd(long long a,long long b,long long &x,long long &y)
{
if(b==0)
{
x=1;
y=0;
return a;
}
long long ans=exgcd(b,a%b,x,y);
long long t=x;
x=y;
y=t-a/b*y;
return ans;
}
int main()
{
scanf("%lld%lld",&a,&b);
num=exgcd(a,b,x,y);
x/=num;
printf("%lld",(x%b+b)%b);
return 0;
}
中国剩余定理
中国剩余定理(CRT)
中国剩余定理主要用于求解如下形式的一元线性同余方程组(
首先我们要求出
/*
* @Author: clorf
* @Date: 2020-09-23 12:22:56
* @Last Modified by: clorf
* @Last Modified time: 2020-09-23 15:41:22
*/
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<ctime>
#include<cctype>
#define INF 1e9
using namespace std;
const int maxn=100010;
const int mod=1e9+7;
const double Pi=acos(-1.0);
const double eps=1e-7;
//1.数组开小
//2.没用long long/爆了类型存储范围
//3.写之前式子没推清楚
//4.变量写错(想当然typo/没想清楚含义)
//5.写之前没想清楚复杂度
template<class T>void read(T &x)
{
x=0;int f=0;char ch=getchar();
while(!isdigit(ch)) {f|=(ch=='-');ch=getchar();}
while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
x=f?-x:x;
return;
}
long long n,a[maxn],m[maxn],x,y,ans;
long long t[maxn],M[maxn],all=1;
long long exgcd(long long a,long long b,long long &x,long long &y)
{
if(!b)
{
x=1;
y=0;
return a;
}
long long ans=exgcd(b,a%b,x,y);
long long t=x;
x=y;
y=t-a/b*y;
return ans;
}
long long inv(long long a,long long p)
{
x=y=0;
long long num=exgcd(a,p,x,y);
return (x%p+p)%p;
}
int main()
{
scanf("%lld",&n);
for(int i=1;i<=n;i++)
{
scanf("%lld%lld",&m[i],&a[i]);
all*=m[i];
}
for(int i=1;i<=n;i++)
M[i]=all/m[i];
for(int i=1;i<=n;i++)
t[i]=inv(M[i],m[i]);
for(int i=1;i<=n;i++)
ans=(ans+a[i]*M[i]%all*t[i]%all)%all;
printf("%lld",(ans%all+all)%all);
return 0;
}
扩展中国剩余定理(EXCRT)
扩展中国剩余定理适用于解如上同余方程时,模数
第一种方法是合并两个同余方程法。我们每次把两个同余方程合并成一个同余方程,这样合并
于是我们得到
这样便将两个方程合并成了一个。多个方程两两合并后会变成一个方程:
令
第二种方法更容易理解。我们假设我们已经求出了前
其中
/*
* @Author: clorf
* @Date: 2020-09-23 15:45:37
* @Last Modified by: clorf
* @Last Modified time: 2020-09-23 17:19:44
*/
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<ctime>
#include<cctype>
#define INF 1e9
using namespace std;
const int maxn=100010;
const double Pi=acos(-1.0);
const double eps=1e-7;
//1.数组开小
//2.没用long long/爆了类型存储范围
//3.写之前式子没推清楚
//4.变量写错(想当然typo/没想清楚含义)
//5.写之前没想清楚复杂度
template<class T>void read(T &x)
{
x=0;int f=0;char ch=getchar();
while(!isdigit(ch)) {f|=(ch=='-');ch=getchar();}
while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
x=f?-x:x;
return;
}
long long n,a[maxn],p[maxn];
long long m,ans,x,y;
long long exgcd(long long a,long long b,long long &x,long long &y)
{
if(!b)
{
x=1;
y=0;
return a;
}
long long ans=exgcd(b,a%b,x,y);
long long t=x;
x=y;
y=t-a/b*y;
return ans;
}
inline long long multi(long long a,long long b,long long p)
{
long long ans=0;
for(;b;b>>=1)
{
if(b&1)
ans=(ans+a)%p;
a=(a+a)%p;
}
return ans%p;
}
int main()
{
scanf("%lld",&n);
for(int i=1;i<=n;i++)
scanf("%lld%lld",&p[i],&a[i]);
m=p[1];
ans=a[1];
for(int i=2;i<=n;i++)
{
long long num=exgcd(m,p[i],x,y);
long long c=(a[i]-ans%p[i]+p[i])%p[i];
long long mod=p[i]/num;
if(c%num)
{
printf("-1");
return 0;
}
x=multi(x,c/num,mod);
x=(x+mod)%mod;
ans+=x*m;
m*=mod;
ans=(ans%m+m)%m;
}
printf("%lld",ans);
return 0;
}
一道扩展中国剩余定理的套路题。先用 set 维护出每次的攻击力,然后就化成了求解一个同余方程组的问题了。如下所示:
其实和扩展中国剩余定理的式子差不多,多了一个系数而已。我们依然按照推扩展中国剩余定理的式子那样去推答案即可。注意每次砍多少刀有个下限,至少一定要砍那么多刀(针对于
/*
* @Author: clorf
* @Date: 2020-09-29 10:24:46
* @Last Modified by: clorf
* @Last Modified time: 2020-09-29 16:55:19
*/
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<ctime>
#include<cctype>
#include<set>
#define INF 1e9
using namespace std;
const int maxn=200010;
const double Pi=acos(-1.0);
const double eps=1e-7;
//1.数组开小
//2.没用long long/爆了类型存储范围
//3.写之前式子没推清楚
//4.变量写错(想当然typo/没想清楚含义)
//5.写之前没想清楚复杂度
template<class T>void read(T &x)
{
x=0;int f=0;char ch=getchar();
while(!isdigit(ch)) {f|=(ch=='-');ch=getchar();}
while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
x=f?-x:x;
return;
}
int t,n,m;
long long a[maxn],p[maxn];
long long r[maxn],mich[11];
multiset<long long> e;
long long exgcd(long long a,long long b,long long &x,long long &y)
{
if(!b)
{
x=1;
y=0;
return a;
}
long long ans=exgcd(b,a%b,x,y);
long long t=x;
x=y;
y=t-a/b*y;
return ans;
}
int main()
{
scanf("%d",&t);
for(int _=1;_<=t;_++)
{
e.clear();
bool flag=0;
long long x,y;
x=y=0;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%lld",&a[i]);
for(int i=1;i<=n;i++)
scanf("%lld",&p[i]);
for(int i=1;i<=n;i++)
scanf("%lld",&r[i]);
for(int i=1;i<=m;i++)
{
scanf("%lld",&x);
e.insert(x);
}
long long atk,all=1,ans=0,maxx=0;
for(int i=1;i<=n;i++)
{
if(e.upper_bound(a[i])==e.begin())
atk=*e.begin();
else
atk=*(--e.upper_bound(a[i]));
long long A=(__int128)atk*all%p[i];
long long B=p[i];
long long C=(a[i]-atk*ans%p[i]+p[i])%p[i];
long long num=exgcd(A,B,x,y);
x=(x%B+B)%B;
if(C%num)
{
flag=1;
break;
}
ans+=(__int128)(C/num)*x%(B/num)*all;
all*=B/num;
ans%=all;
maxx=max(maxx,(a[i]-1)/atk+1);//每次至少要砍这么多刀
e.erase(e.find(atk));
e.insert(r[i]);
}
if(ans<maxx)
ans=maxx;
if(flag)
{
mich[_]=-1;
continue;
}
mich[_]=ans;
}
for(int i=1;i<=t;i++)
printf("%lld\n",mich[i]);
return 0;
}
BSGS
BSGS
BSGS(baby step giant step),中文译作大步小步算法。它能在
其中
我们令
我们枚举 map 或哈希表中,然后枚举 map 或哈希表中是否有相等的
/*
* @Author: clorf
* @Date: 2020-09-24 16:33:30
* @Last Modified by: clorf
* @Last Modified time: 2020-09-24 19:02:09
*/
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<ctime>
#include<cctype>
#include<unordered_map>
#define INF 1e9
using namespace std;
const int maxn=100010;
const double Pi=acos(-1.0);
const double eps=1e-7;
//1.数组开小
//2.没用long long/爆了类型存储范围
//3.写之前式子没推清楚
//4.变量写错(想当然typo/没想清楚含义)
//5.写之前没想清楚复杂度
template<class T>void read(T &x)
{
x=0;int f=0;char ch=getchar();
while(!isdigit(ch)) {f|=(ch=='-');ch=getchar();}
while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
x=f?-x:x;
return;
}
namespace BSGS
{
long long a,b,p;
inline long long power(long long a,long long b,long long p)
{
long long ans=1;
for(;b;b>>=1)
{
if(b&1)
ans=ans*a%p;
a=a*a%p;
}
return ans%p;
}
inline long long BSGS(long long a,long long b,long long p)
{
unordered_map<long long,int> hash;
b%=p;
int t=sqrt(p)+1;
long long val=b;
hash[val]=0;
for(int j=1;j<t;j++)
{
val=val*a%p;
hash[val]=j;
}
if(a==0)
{
if(b==0)
return 1;
else
return -1;
}
long long num=power(a,t,p)%p;
val=1;
for(int i=1;i<=t;i++)
{
val=val*num%p;
int j=hash.find(val)==hash.end()?-1:hash[val];
if(j>=0&&i*t-j>=0)
return i*t-j;
}
return -1;
}
inline void solve()
{
scanf("%lld%lld%lld",&p,&a,&b);
long long ans=BSGS(a,b,p);
if(ans>=0)
printf("%lld",ans);
else
printf("no solution");
}
}
int main()
{
BSGS::solve();
return 0;
}
扩展BSGS
即求解上题
对于
若
注意在这一步中如果某一步
此时由于
/*
* @Author: clorf
* @Date: 2020-09-24 17:50:47
* @Last Modified by: clorf
* @Last Modified time: 2020-09-24 18:55:07
*/
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<ctime>
#include<cctype>
#include<unordered_map>
#define INF 1e9
using namespace std;
const int maxn=100010;
const double Pi=acos(-1.0);
const double eps=1e-7;
//1.数组开小
//2.没用long long/爆了类型存储范围
//3.写之前式子没推清楚
//4.变量写错(想当然typo/没想清楚含义)
//5.写之前没想清楚复杂度
template<class T>void read(T &x)
{
x=0;int f=0;char ch=getchar();
while(!isdigit(ch)) {f|=(ch=='-');ch=getchar();}
while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
x=f?-x:x;
return;
}
long long a,b,p;
inline long long power(long long a,long long b,long long p)
{
long long ans=1;
for(;b;b>>=1)
{
if(b&1)
ans=ans*a%p;
a=a*a%p;
}
return ans;
}
inline long long gcd(long long a,long long b)
{
if(!b)
return a;
return gcd(b,a%b);
}
long long exgcd(long long a,long long b,long long &x,long long &y)
{
if(!b)
{
x=1;
y=0;
return a;
}
long long ans=exgcd(b,a%b,x,y);
long long t=x;
x=y;
y=t-a/b*y;
return ans;
}
inline long long inv(long long a,long long p)
{
long long x,y;
x=y=0;
exgcd(a,p,x,y);
return (x%p+p)%p;
}
inline long long BSGS(long long a,long long b,long long p,long long k)
{
unordered_map<long long,int> hash;
hash.clear();
b%=p;
int t=sqrt(p)+1;
long long val=b;
hash[val]=0;
for(int j=1;j<t;j++)
{
val=val*a%p;
hash[val]=j;
}
if(a==0)
{
if(b==0)
return 1;
else
return -1;
}
long long num=power(a,t,p)%p;
val=1;
for(int i=1;i<=t;i++)
{
val=val*num%p;
int j=hash.find(val)==hash.end()?-1:hash[val];
if(j>=0&&i*t-j+k>=0)
return i*t-j+k;
}
return -1;
}
inline long long exBSGS(long long a,long long b,long long p)
{
if(b==1)
return 0;
long long k=0,x=1,num=1;
while(1)
{
long long d=gcd(a,p);
if(d==1)
break;
if(b%d)
return -1;
p/=d;
b/=d;
k++;
x=x*d%p;
num=num*a/d%p;
if(num==b)
return k;
}
long long sum=power(a,k,p)*inv(x,p)%p;
b=b*inv(sum,p)%p;
return BSGS(a,b,p,k);
}
inline void solve()
{
while(scanf("%lld%lld%lld",&a,&p,&b)!=EOF&&a+p+b)
{
long long ans=exBSGS(a,b,p);
if(ans>=0)
printf("%lld\n",ans);
else
printf("No Solution\n");
}
}
int main()
{
solve();
return 0;
}
卢卡斯定理
卢卡斯(
卢卡斯定理
对于质数
对于
/*
* @Author: clorf
* @Date: 2020-09-23 19:14:09
* @Last Modified by: clorf
* @Last Modified time: 2020-09-23 19:41:41
*/
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<ctime>
#include<cctype>
#define INF 1e9
using namespace std;
const int maxn=100010;
const double Pi=acos(-1.0);
const double eps=1e-7;
//1.数组开小
//2.没用long long/爆了类型存储范围
//3.写之前式子没推清楚
//4.变量写错(想当然typo/没想清楚含义)
//5.写之前没想清楚复杂度
template<class T>void read(T &x)
{
x=0;int f=0;char ch=getchar();
while(!isdigit(ch)) {f|=(ch=='-');ch=getchar();}
while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
x=f?-x:x;
return;
}
int t;
long long fac[maxn],inv[maxn],n,m,p;
long long power(long long a,long long b,long long p)
{
long long ans=1%p;
for(;b;b>>=1)
{
if(b&1)
ans=ans*a%p;
a=a*a%p;
}
return ans%p;
}
long long C(long long n,long long m,long long p)
{
return fac[n]*inv[m]%p*inv[n-m]%p;
}
long long lucas(long long n,long long m,long long p)
{
if(!m)
return 1;
return lucas(n/p,m/p,p)*C(n%p,m%p,p)%p;
}
int main()
{
scanf("%d",&t);
while(t--)
{
scanf("%lld%lld%lld",&n,&m,&p);
fac[0]=inv[0]=1;
for(int i=1;i<=n+m;i++)
{
fac[i]=fac[i-1]*i%p;
inv[i]=power(fac[i],p-2,p);
}
printf("%lld\n",lucas(n+m,n,p));
}
return 0;
}
//有些阶乘是p的倍数,就不存在逆元,因此不能用阶乘逆元倒推。
给定整数
首先
首先已知
枚举
/*
* @Author: clorf
* @Date: 2020-09-25 15:24:22
* @Last Modified by: clorf
* @Last Modified time: 2020-09-25 15:24:22
*/
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<ctime>
#include<cctype>
#define INF 1e9
using namespace std;
const int maxn=40010;
const int mod=999911658;
const double Pi=acos(-1.0);
const double eps=1e-7;
//1.数组开小
//2.没用long long/爆了类型存储范围
//3.写之前式子没推清楚
//4.变量写错(想当然typo/没想清楚含义)
//5.写之前没想清楚复杂度
template<class T>void read(T &x)
{
x=0;int f=0;char ch=getchar();
while(!isdigit(ch)) {f|=(ch=='-');ch=getchar();}
while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
x=f?-x:x;
return;
}
long long n,g;
long long m[5]={0,2,3,4679,35617};
long long a[5],fac[maxn];
inline long long power(long long a,long long b,long long p)
{
long long ans=1;
for(;b;b>>=1)
{
if(b&1)
ans=ans*a%p;
a=a*a%p;
}
return ans%p;
}
inline long long inv(long long x,long long p)
{
return power(x,p-2,p);
}
inline long long C(long long n,long long m,long long p)
{
if(n<m)
return 0;
return fac[n]*inv(fac[m],p)%p*inv(fac[n-m],p)%p;
}
long long lucas(long long n,long long m,long long p)
{
if(n<m)
return 0;
if(!n)
return 1;
return lucas(n/p,m/p,p)%p*C(n%p,m%p,p)%p;
}
inline void ready(int n)
{
fac[0]=1;
for(int i=1;i<=n;i++)
fac[i]=fac[i-1]*i%n;
}
inline long long CRT()
{
long long ans=0;
for(int i=1;i<=4;i++)
ans=(ans+a[i]*(mod/m[i])%mod*power(mod/m[i],m[i]-2,m[i]))%mod;
return ans;
}
int main()
{
scanf("%lld%lld",&n,&g);
if(g==mod+1)
{
printf("0");
return 0;
}
for(int i=1;i<=4;i++)
{
ready(m[i]);
for(int j=1;j*j<=n;j++)
if(n%j==0)
{
a[i]=(a[i]+lucas(n,j,m[i])%m[i])%m[i];
int k=n/j;
if(j!=k)
a[i]=(a[i]+lucas(n,k,m[i])%m[i])%m[i];
}
}
long long ans=CRT();
printf("%lld",power(g,ans,mod+1));
return 0;
}
扩展卢卡斯定理
扩展卢卡斯定理用于解决上题
首先对
我们可以用中国剩余定理求出
接下来就是求
其中
现在问题就转化到了这里,如何求以下式子:
我们先考虑如何算
通过把
循环节共循环了
最后由于第一部分要除掉,我们将第二和第三部分乘起来即可,注意第二部分要递归求解,因为在这个阶乘里可能还有
最后回到这个式子:
借助上面的方法求出分子分母三个式子,求出逆元与
然后质因数分解对每个质数这样来一遍,中国剩余定理求解。
(https://www.luogu.com.cn/problem/P4720)[Luogu4720 扩展卢卡斯]
/*
* @Author: clorf
* @Date: 2020-09-23 19:42:16
* @Last Modified by: clorf
* @Last Modified time: 2020-09-23 22:40:40
*/
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<ctime>
#include<cctype>
#define INF 1e9
using namespace std;
const int maxn=1000100;
const double Pi=acos(-1.0);
const double eps=1e-7;
//1.数组开小
//2.没用long long/爆了类型存储范围
//3.写之前式子没推清楚
//4.变量写错(想当然typo/没想清楚含义)
//5.写之前没想清楚复杂度
template<class T>void read(T &x)
{
x=0;int f=0;char ch=getchar();
while(!isdigit(ch)) {f|=(ch=='-');ch=getchar();}
while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
x=f?-x:x;
return;
}
namespace EXlucas
{
long long n,m,p,a[maxn],mod[maxn],cnt,fac[maxn];
inline long long power(long long a,long long b,long long p)
{
long long ans=1;
for(;b;b>>=1)
{
if(b&1)
ans=ans*a%p;
a=a*a%p;
}
return ans%p;
}
long long exgcd(long long a,long long b,long long &x,long long &y)
{
if(!b)
{
x=1;
y=0;
return a;
}
long long ans=exgcd(b,a%b,x,y);
long long t=x;
x=y;
y=t-a/b*y;
return ans;
}
inline long long inv(long long a,long long p)
{
long long x,y;
x=y=0;
long long num=exgcd(a,p,x,y);
return (x%p+p)%p;
}
inline long long CRT()
{
long long all=1,ans=0;
for(int i=1;i<=cnt;i++)
all*=mod[i];
for(int i=1;i<=cnt;i++)
ans=(ans+a[i]*(all/mod[i])%all*inv(all/mod[i],mod[i])%all)%all;
return ans;
}
long long calc(long long n,long long p,long long pk)
{
if(!n)
return 1;
if(n<p)
return fac[n];
long long ans;
ans=power(fac[pk-1],n/pk,pk);
ans=ans*fac[n%pk]%pk;
return ans*calc(n/p,p,pk)%pk;
}
inline long long C(long long n,long long m,long long p,long long pk)
{
if(n<m)
return 0;
fac[0]=1;
for(int i=1;i<=pk-1;i++)
{
if(i%p)
fac[i]=fac[i-1]*i%pk;
else
fac[i]=fac[i-1];
}//预处理!!!
long long num1=calc(n,p,pk);
long long num2=calc(m,p,pk);
long long num3=calc(n-m,p,pk);
long long num=0;
for(long long i=n;i;i/=p)
num+=i/p;
for(long long i=m;i;i/=p)
num-=i/p;
for(long long i=n-m;i;i/=p)
num-=i/p;
long long ans=num1*inv(num2,pk)%pk*inv(num3,pk)%pk*power(p,num,pk)%pk;
return ans;
}
long long exlucas(long long n,long long m,long long p)
{
cnt=0;
long long q=sqrt(p);
for(int i=2;p>1&&i<=q;i++)
{
long long num=1;
while(p%i==0)
{
p/=i;
num*=i;
}
if(num>1)
{
a[++cnt]=C(n,m,i,num);
mod[cnt]=num;
}
}
if(p>1)
{
a[++cnt]=C(n,m,p,p);
mod[cnt]=p;
}
return CRT();
}
inline void solve()
{
scanf("%lld%lld%lld",&n,&m,&p);
printf("%lld\n",exlucas(n,m,p));
}
};
int main()
{
EXlucas::solve();
return 0;
}
裸的扩展卢卡斯,推出答案的式子为如下所示:
直接扩展卢卡斯。
/*
* @Author: clorf
* @Date: 2020-09-29 19:44:20
* @Last Modified by: clorf
* @Last Modified time: 2020-09-29 19:44:20
*/
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<ctime>
#include<cctype>
#define INF 1e9
using namespace std;
const int maxn=7;
const double Pi=acos(-1.0);
const double eps=1e-7;
//1.数组开小
//2.没用long long/爆了类型存储范围
//3.写之前式子没推清楚
//4.变量写错(想当然typo/没想清楚含义)
//5.写之前没想清楚复杂度
template<class T>void read(T &x)
{
x=0;int f=0;char ch=getchar();
while(!isdigit(ch)) {f|=(ch=='-');ch=getchar();}
while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
x=f?-x:x;
return;
}
namespace EXlucas
{
long long a[maxn],mod[maxn],cnt;
inline long long power(long long a,long long b,long long p)
{
long long ans=1;
for(;b;b>>=1)
{
if(b&1)
ans=ans*a%p;
a=a*a%p;
}
return ans%p;
}
long long exgcd(long long a,long long b,long long &x,long long &y)
{
if(!b)
{
x=1;
y=0;
return a;
}
long long ans=exgcd(b,a%b,x,y);
long long t=x;
x=y;
y=t-a/b*y;
return ans;
}
inline long long inv(long long a,long long p)
{
long long x,y;
x=y=0;
long long num=exgcd(a,p,x,y);
return (x%p+p)%p;
}
inline long long CRT()
{
long long all=1,ans=0;
for(int i=1;i<=cnt;i++)
all*=mod[i];
for(int i=1;i<=cnt;i++)
ans=(ans+a[i]*(all/mod[i])%all*inv(all/mod[i],mod[i])%all)%all;
return ans;
}
long long fac(long long n,long long p,long long pk)
{
if(!n)
return 1;
long long ans=1;
for(int i=1;i<pk;i++)
if(i%p)
ans=ans*i%pk;
ans=power(ans,n/pk,pk);
for(int i=1;i<=n%pk;i++)
if(i%p)
ans=ans*i%pk;
return ans*fac(n/p,p,pk)%pk;
}
inline long long C(long long n,long long m,long long p,long long pk)
{
if(n<m)
return 0;
long long num1=fac(n,p,pk);
long long num2=fac(m,p,pk);
long long num3=fac(n-m,p,pk);
long long num=0;
for(long long i=n;i;i/=p)
num+=i/p;
for(long long i=m;i;i/=p)
num-=i/p;
for(long long i=n-m;i;i/=p)
num-=i/p;
long long ans=num1*inv(num2,pk)%pk*inv(num3,pk)%pk*power(p,num,pk)%pk;
return ans;
}
long long exlucas(long long n,long long m,long long p)
{
cnt=0;
long long q=sqrt(p);
for(int i=2;p>1&&i<=q;i++)
{
long long num=1;
while(p%i==0)
{
p/=i;
num*=i;
}
if(num>1)
{
a[++cnt]=C(n,m,i,num);
mod[cnt]=num;
}
}
if(p>1)
{
a[++cnt]=C(n,m,p,p);
mod[cnt]=p;
}
return CRT();
}
}
int mod,n,m;
long long w[maxn],ans=1,sum;
int main()
{
scanf("%d%d%d",&mod,&n,&m);
for(int i=1;i<=m;i++)
{
scanf("%lld",&w[i]);
sum+=w[i];
}
if(sum>n)
{
printf("Impossible\n");
return 0;
}
for(int i=1;i<=m;i++)
{
ans=(__int128)(ans*EXlucas::exlucas(n,w[i],mod))%mod;
n-=w[i];
}
printf("%lld\n",ans);
return 0;
}
拉格朗日插值
其实严格来说应该不算数论知识,应该算多项式。
拉格朗日插值能将多项式的点值表达式转为系数表达式。
给出
我们用待定系数法可以变成一个
但是这种方法的复杂度太高了,我们需要更简单的方法。
拉格朗日插值能在
我们直接将
/*
* @Author: clorf
* @Date: 2020-09-23 17:53:58
* @Last Modified by: clorf
* @Last Modified time: 2020-09-23 18:05:23
*/
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<ctime>
#include<cctype>
#define INF 1e9
using namespace std;
const int maxn=2010;
const double Pi=acos(-1.0);
const int mod=998244353;
const double eps=1e-7;
//1.数组开小
//2.没用long long/爆了类型存储范围
//3.写之前式子没推清楚
//4.变量写错(想当然typo/没想清楚含义)
//5.写之前没想清楚复杂度
template<class T>void read(T &x)
{
x=0;int f=0;char ch=getchar();
while(!isdigit(ch)) {f|=(ch=='-');ch=getchar();}
while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
x=f?-x:x;
return;
}
long long n,k,ans;
long long x[maxn],y[maxn];
long long power(long long a,long long b,long long p)
{
long long ans=1;
for(;b;b>>=1)
{
if(b&1)
ans=ans*a%p;
a=a*a%p;
}
return ans%p;
}
long long inv(long long x)
{
return power(x,mod-2,mod);
}
int main()
{
scanf("%lld%lld",&n,&k);
for(int i=1;i<=n;i++)
scanf("%lld%lld",&x[i],&y[i]);
for(int i=1;i<=n;i++)
{
long long sum1=y[i]%mod;
long long sum2=1;
for(int j=1;j<=n;j++)
if(i!=j)
{
sum1=sum1*(k-x[j])%mod;
sum2=sum2*((x[i]%mod-x[j]%mod)%mod)%mod;
}
long long sum=sum1*inv(sum2)%mod;
ans=(ans+sum)%mod;
ans=(ans+mod)%mod;
}
printf("%lld",ans);
return 0;
}
这道题值得一做。首先易知
其中出现了
同时对于这种
如何快速计算
对于分母来说,可以发现这就是阶乘的形式,那么式子即是:
需要注意的是分母可能会出现符号问题,当
最后这道题有一个小细节,如果末尾有一段连续空位,应该去掉它,因为它不需要多余的亵渎。
/*
* @Author: clorf
* @Date: 2020-09-24 19:27:06
* @Last Modified by: clorf
* @Last Modified time: 2020-09-24 20:34:05
*/
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<ctime>
#include<cctype>
#define INF 1e9
using namespace std;
const int maxn=55;
const int mod=1e9+7;
const double Pi=acos(-1.0);
const double eps=1e-7;
//1.数组开小
//2.没用long long/爆了类型存储范围
//3.写之前式子没推清楚
//4.变量写错(想当然typo/没想清楚含义)
//5.写之前没想清楚复杂度
template<class T>void read(T &x)
{
x=0;int f=0;char ch=getchar();
while(!isdigit(ch)) {f|=(ch=='-');ch=getchar();}
while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
x=f?-x:x;
return;
}
int t;
long long n,m,a[maxn];
long long k,d,fac[maxn];
long long y[maxn],ans;
long long pre[maxn],suf[maxn];
inline long long power(long long a,long long b,long long p)
{
long long ans=1;
for(;b;b>>=1)
{
if(b&1)
ans=ans*a%p;
a=a*a%p;
}
return ans%p;
}
inline long long Lagrange(long long x)//处理连续区间的插值
{
pre[0]=1;
suf[d+1]=1;
for(int i=1;i<=d;i++)
pre[i]=(pre[i-1]*(x-i)%mod+mod)%mod;
for(int i=d;i>=1;i--)
suf[i]=(suf[i+1]*(x-i)%mod+mod)%mod;
long long sum1,sum2,t1,t2;
sum1=sum2=t2=0;//分子分母
t1=1;
for(int i=1;i<=d;i++)
{
sum2=pre[i-1]*suf[i+1]%mod*y[i]%mod;
t2=fac[i-1]*fac[d-i]%mod;
if((d-i)&1)//d-i奇数,变号
t2=(mod-t2)%mod;
sum1=(sum1*t2%mod+sum2*t1%mod)%mod;
t1=t1*t2%mod;//通分
}
return sum1*power(t1,mod-2,mod);
}
int main()
{
scanf("%d",&t);
while(t--)
{
scanf("%lld%lld",&n,&m);
for(int i=1;i<=m;i++)
scanf("%lld",&a[i]);
sort(a+1,a+m+1);
if(a[m]==n)
{
n--;
m--;
}//把末尾空格去掉
k=m+1;//需要用m+1张
d=k+2;//k+1次多项式需要这么多点
y[0]=0;
for(int i=1;i<=d;i++)
y[i]=(y[i-1]+power(i,k,mod))%mod;
fac[0]=1;
for(int i=1;i<=d;i++)
fac[i]=fac[i-1]*i%mod;
ans=0;
for(int i=0;i<=m;i++)
{
ans=(ans+Lagrange(n-a[i]))%mod;
for(int j=i+1;j<=m;j++)
ans=(ans-power(a[j]-a[i],k,mod)+mod)%mod;
}
printf("%lld\n",ans);
}
return 0;
}