费马小定理 & 欧拉定理

Part 1:知识点

费马小定理

p 为质数,ap,则 ap11(modp)

欧拉定理

an,则 aφ(n)1(modn)

不会欧拉函数的点这里

证明:

{r1¯,r2¯,,rφ(n)¯n 的一个简化剩余系,则 {ar1¯,ar2¯,,arφ(n)}¯ 也为 n 的一个简化剩余系。
所以 r1r2rφ(n)ar1ar2arφ(n)aφ(n)r1r2rφ(n)(modm)
约去 r1r2rφ(n),可得 aφ(n)1(modn)

p 是质数时,φ(p)=p1,代入欧拉定理中可得费马小定理

扩展欧拉定理

ab{abmodφ(n)gcd(a,n)=1abgcd(a,n)1,b<φ(n)abmodφ(n)+φ(n)gcd(a,n)1,bφ(n)(modn)

一道试水模板题【模板】扩展欧拉定理

Part 2:一些习题

1、P4861 按钮

求最小的 x 使得 ax1(modm)

Bézout 定理知当 gcd(a,m)1 时无解

由欧拉定理知 aφ(m)1(modm)

φ(m) 不一定是最小的 x。不过我们可以大概猜出 xφ(m),下面就来证明一下:

证明:x 为最小的解,且 xφ(m)
那么显然 aφ(m)kx1(modm)(kx<φ(m))
则有 aφ(m)modx1(modm)
此时 φ(m)modx<x,这与 x 是最小的解矛盾
所以 xφ(m)

那我们可以 O(φ(m)) 暴力枚举每个约数,快速幂验证一下即可

#include<bits/stdc++.h>
using namespace std;

int a,m,p,ans;

int gcd(int x,int y)
{
	if(y==0)
		return x;
	return gcd(y,x%y);
}

int get_phi(int x)
{
    int res=x;
    for(int i=2; i*i<=x; i++)
    {
        if(x%i==0)
        {
            res=res/i*(i-1);
            while(x%i==0)
                x/=i;
        }
    }

    if(x>1)
        res=res/x*(x-1);
    return res;
}

int ksm(int x,int y)
{
	if(y==0)	
		return 1;
	if(y==1)
		return x%m;
	int tmp=ksm(x,y/2);
	if(y%2==0)
		return 1LL*tmp*tmp%m;
	return 1LL*tmp*tmp%m*x%m;
}

int main()
{
	scanf("%d%d",&m,&a);
	
	p=get_phi(m);
	ans=p;
	
	if(gcd(a,m)!=1)
	{
		printf("Let's go Blue Jays!");
		return 0;
	}
	
	for(int i=1; i*i<=p; i++)
	{
		if(p%i!=0)
			continue;
		
		if(ksm(a,i)==1)
			ans=min(ans,i);
		else if(ksm(a,p/i)==1)
			ans=min(ans,p/i);
	}
	
	printf("%d",ans);
	
	return 0;
}

2、P4139 上帝与集合的正确用法

即求 2222modp

因为幂塔是无穷的,显然大于 φ(p),根据扩展欧拉定理有 22222222modφ(p)+φ(p)(modp)

显然是个递归的式子。又因为欧拉函数不断地迭代最终必定取到 1,所以递归暴力计算即可

#include<bits/stdc++.h>
using namespace std;

const int N=10000010;

int T,p,phi[N],v[N],prime[N],tot;

void primes()
{
	phi[1]=1;
	for(int i=2; i<=1e7; i++)
	{
		if(!v[i])
		{
			v[i]=i;
			prime[++tot]=i;
			phi[i]=i-1;
		}
		
		for(int j=1; j<=tot; j++)
		{
			if(prime[j]>v[i] || prime[j]>1e7/i)
				break;
			
			v[i*prime[j]]=prime[j];
			if(i%prime[j]==0)
				phi[i*prime[j]]=phi[i]*prime[j];
			else
				phi[i*prime[j]]=phi[i]*(prime[j]-1);
		}
	}
} 

int pow(int a,int b,int mod)
{
	if(b==0)
		return 1;
	if(b==1)
		return a%mod;
	int tmp=pow(a,b/2,mod);
	if(b%2==0)
		return 1LL*tmp*tmp%mod;
	return 1LL*tmp*tmp%mod*a%mod;
}

int calc(int n)
{
	if(n==1)
		return 0;

	return pow(2,calc(phi[n])+phi[n],n);
}

int main()
{
	primes();
	
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d",&p);
		printf("%d\n",calc(p));
	}
	
	return 0;
}

3、P3934 [Ynoi2016] 炸脖龙 I

我愿称之为相逢是问候的前置题

操作 1 构造差分数组,树状数组修改即可。下面重点考虑操作 2

根据第2题的结论,幂塔不断迭代最后为定值,而迭代次数为使得 φ(φ(φ(p)))=1 的迭代次数,是 logp 级别

因此我们考虑像上一题一样暴力递归即可。但注意,此时指数不一定大于等于 φ(p),所以要加一个全局变量 flag 记录一下。直接在快速幂中记录即可。具体实现时可以先不取模,判断完再取模

代码实现细节多,写在代码注释中

#include<bits/stdc++.h>
#define LL long long
using namespace std;

const int N=500010,M=20000010;

int n,m,phi[M],prime[M],tot,v[M];
bool flag;
LL c[N];

void prework() //预处理1~2e7所有的phi值
{
    phi[1]=1;
    for(int i=2; i<=2e7; i++)
    {
        if(!v[i])
        {
            v[i]=i;
            prime[++tot]=i;
            phi[i]=i-1;
        }

        for(int j=1; j<=tot; j++)
        {
            if(prime[j]>v[i] || prime[j]>2e7/i)
                break;
            v[i*prime[j]]=prime[j];
            if(i%prime[j]==0)
                phi[i*prime[j]]=prime[j]*phi[i];
            else
                phi[i*prime[j]]=(prime[j]-1)*phi[i];
        } 
    }
}

void add(int x,int y)
{
	for(x; x<=n+1; x+=(x&-x))
		c[x]+=(LL)y;
}

LL ask(int x)
{
	LL res=0;
	for(x; x; x-=(x&-x))
		res+=c[x];
	return res;	
}

LL ksm(LL x,LL y,LL mod)
{
	flag=0;
	LL res=1;
	if(x>=mod) //大坑点!!!!
		x%=mod,flag=1;
	while(y)
	{
		if(y&1)
			res*=x;
		x*=x;
		if(res>=mod) //判断两次
			res%=mod,flag=1;
		if(x>=mod)
			x%=mod,flag=1;
		y>>=1;
	} 
	
	return res;
}

LL dfs(int now,LL p,int k)
{
	flag=0;
	LL cur=ask(now);
	if(p==1 || now==k)
	{
		if(cur>=p) //注意要判断
			flag=1,cur%=p;
		return cur;
	}
	
	LL tur=dfs(now+1,phi[p],k);
	return ksm(cur,flag? tur+(LL)phi[p]:tur,p); //注意指数选择的情况
}

int main()
{
	prework();
	
	scanf("%d%d",&n,&m);
	for(int i=1,last=0; i<=n; i++)
	{
		int x;
		scanf("%d",&x);
		add(i,x-last); //差分数组
		last=x;
	}
	
	for(int i=1; i<=m; i++)
	{
		int op,l,r,x,p;
		scanf("%d%d%d",&op,&l,&r);
		
		if(op==1)
		{
			scanf("%d",&x);
			add(l,x);  add(r+1,-x);
		}
		else
		{
			scanf("%d",&p);
			printf("%lld\n",dfs(l,(LL)p,r));
		}		
	}
	
	return 0;
}

4、P3747 [六省联考 2017] 相逢是问候

一道线段树+扩展欧拉定理的毒瘤题

建议先做P4145 上帝造题的七分钟 2 / 花神游历各国

操作 1 一眼线段树。重点考虑操作 0

根据第2题的结论,cccai 不断迭代最后为定值,而迭代次数 tphi 为使得 φ(φ(φ(p)))=1 的迭代次数,是 logp 级别

因此,类似上帝造题的7分钟2,我们对线段树的每个叶子节点记录一个迭代次数 t,区间的 t 取左右儿子的最小值。当区间的 t 大于等于 tphi 时不递归。对于叶子节点的修改,类似上一题一样 dfs 迭代 t 次即可。欧拉函数可预处理出来,只预处理有用的即可。注意因为最顶上的数是 ai 不是 c,所以欧拉函数最后要放两个 1

注意到这题的指数不一定大于 φ(p),像上一题一样判断即可,实现略有出入

时间复杂度为 O(nlog3p),每个叶子节点修改 nlogp,每个位置递归欧拉函数 logp 层,计算时还得快速幂 logp。这样显然无法通过。注意到只有 logp 种模数,所以可以将快速幂换成光速幂,即 ca=c10000×k×ct。这样可以 O(p) 预处理,O(1) 回答,省去一个 log

#include<bits/stdc++.h>
#define LL long long
#define lc(p)  p*2
#define rc(p)  p*2+1
using namespace std;

const int N=50010,M=50,MAXN=10010;

struct SegmentTree
{
	LL sum,t;
	#define sum(x)  tree[x].sum
	#define t(x)  tree[x].t
}tree[4*N];
int n,m,phi[M],tphi;
LL P,c,a[4*N],pow1[MAXN][M],pow2[MAXN][M];
bool b1[MAXN][M],b2[MAXN][M],flag;

int get_phi(int x)
{
	int res=x;
	for(int i=2; i*i<=x; i++)
	{
		if(x%i==0)
		{
			res=res/i*(i-1);
			while(x%i==0)
				x/=i;
		}
	}
	
	if(x>1)
		res=res/x*(x-1);
	return res;
}

void prework()
{
	LL tmp=P;
	phi[0]=P; //预处理有用的phi
	while(tmp!=1)
		tmp=get_phi(tmp),phi[++tphi]=tmp;
	phi[++tphi]=1;
		
	for(int i=0; i<=tphi; i++) //光速幂
	{
		pow1[0][i]=1;
		for(int j=1; j<=10000; j++)
		{
			pow1[j][i]=pow1[j-1][i]*c;
			if(pow1[j][i]>=phi[i])
				pow1[j][i]%=phi[i],b1[j][i]=1;
			b1[j][i]|=b1[j-1][i]; //判断是扩展欧拉定理的哪种情况
		}
	}
	
	for(int i=0; i<=tphi; i++)
	{
		pow2[0][i]=1;
		b2[1][i]=b1[10000][i];
		for(int j=1; j<=10000; j++)
		{
			pow2[j][i]=pow2[j-1][i]*pow1[10000][i];
			if(pow2[j][i]>=phi[i])
				pow2[j][i]%=phi[i],b2[j][i]=1;
			b2[j][i]|=b2[j-1][i];
		}
	}
}

LL calc(LL x,int k) //O(1)回答
{
	flag=0;
	LL x1=x%10000,x2=x/10000;
	LL res=pow1[x1][k]*pow2[x2][k];
	if(res>=phi[k])
		res%=phi[k],flag=1;
	flag|=b1[x1][k]|b2[x2][k];
	
	return res;
}

LL dfs(LL cur,int dep,int k) //递归log_p层
{
	flag=0;
	if(dep==k)
	{
		if(cur>=phi[dep])
			flag=1,cur%=phi[dep];
		return cur;
	}
	
	LL tur=dfs(cur,dep+1,k);
	return calc(flag? tur+phi[dep+1]:tur,dep);
}

void pushup(int p)
{
	t(p)=min(t(lc(p)),t(rc(p)));
	sum(p)=(sum(lc(p))+sum(rc(p)))%P;
}

void build(int p,int l,int r)
{
	if(l==r)
	{
		scanf("%d",&a[p]);
		sum(p)=a[p];
		return;
	}
	
	int mid=(l+r)>>1;
	build(lc(p),l,mid);
	build(rc(p),mid+1,r);
	
	pushup(p);
}

void change(int p,int l,int r,int ql,int qr)
{
	if(t(p)>=tphi) //没有必要继续修改
		return;
		
	if(l==r)
	{
		t(p)++;
		sum(p)=dfs(a[p],0,t(p));
		return;
	}
	
	int mid=(l+r)>>1;
	if(ql<=mid && t(lc(p))<tphi)
		change(lc(p),l,mid,ql,qr);
	if(qr>mid && t(rc(p))<tphi)
		change(rc(p),mid+1,r,ql,qr);
	
	pushup(p);
}

LL ask(int p,int l,int r,int ql,int qr)
{
	if(ql<=l && qr>=r)
		return sum(p);
	
	int mid=(l+r)>>1;
	LL val=0;
	if(ql<=mid)
		val+=ask(lc(p),l,mid,ql,qr);
	if(qr>mid)
		val+=ask(rc(p),mid+1,r,ql,qr);
	
	return val%P;
} 

int main()
{
	scanf("%d%d%lld%lld",&n,&m,&P,&c);
	
	prework();
	
	build(1,1,n);
	
	for(int i=1; i<=m; i++)
	{
		int op,l,r;
		scanf("%d%d%d",&op,&l,&r);
		
		if(op==0)
			change(1,1,n,l,r);
		else
			printf("%lld\n",ask(1,1,n,l,r));
	}
	
	return 0;
}

**

posted @   xishanmeigao  阅读(29)  评论(0编辑  收藏  举报
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
点击右上角即可分享
微信分享提示