BZOJ1319Sgu261Discrete Roots——BSGS+exgcd+原根与指标+欧拉定理

题目描述

给出三个整数p,k,a,其中p为质数,求出所有满足x^k=a (mod p),0<=x<=p-1的x。

输入

三个整数p,k,a。

输出

第一行一个整数,表示符合条件的x的个数。 第二行开始每行一个数,表示符合条件的x,按从小到大的顺序输出。

样例输入

11 3 8

样例输出

1
2

提示

2<=p<p<=10^9
 2<=k<=100000,0<=a

 

首先求出$p$的原根$g$,再求出$a$的指标$b$,即$g^b\equiv a(mod\ p)$。我们知道对于$[0,p-1]$中任意数都能用原根的幂次表示,所以将$x$表示成$g^y$即$g^y\equiv x(mod\ p)$,那么原式就变成了$(g^y)^k\equiv g^b(mod\ p)->g^{yk}\equiv g^b(mod\ p)$。根据欧拉定理可知$g^{p-1}\equiv 1(mod\ p)$,所以$yk\equiv b(mod\ (p-1))$,只需要用$exgcd$求出$[0,p-1]$内所有的$y$即可。

#include<set>
#include<map>
#include<queue>
#include<stack>
#include<cmath>
#include<cstdio>
#include<vector>
#include<bitset>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;
ll p,k,a;
ll g,f;
ll prime[100010];
int tot;
ll q[100010];
int cnt;
map<ll,int>b;
ll quick(ll x,ll y)
{
	ll res=1ll;
	while(y)
	{
		if(y&1)
		{
			res=res*x%p;
		}
		y>>=1;
		x=x*x%p;
	}
	return res;
}
ll gcd(ll x,ll y)
{
	return y==0?x:gcd(y,x%y);
}
void exgcd(ll A,ll B,ll &x,ll &y)
{
    if(!B)
    {
        x=1;
        y=0;
        return ;
    }
    exgcd(B,A%B,y,x);
    y-=(A/B)*x;
}
int main()
{
	scanf("%lld%lld%lld",&p,&k,&a);
	if(!a)
	{
		printf("1\n0");
		return 0;
	}
	ll m=p-1;
	for(int i=2;1ll*i*i<=m;i++)
	{
		if(m%i==0)
		{
			prime[++tot]=i;
			while(m%i==0) 
			{
				m/=i;
			}
		}
	}
	if(m!=1) 
	{
		prime[++tot]=m;
	}
	for(int i=2;i<=p-1;i++)
	{
		bool flag=true;
		for(int j=1;j<=tot;j++) 
		{
			if(quick(i,(p-1)/prime[j])==1)
			{
				flag=false;
				break;
			}
		}
		if(flag)
		{
			g=i;
			break;
		}
	}
	int n=ceil(sqrt(p));
	ll sum=1ll;
	for(int i=1;i<=n;i++)
	{
		(sum*=g)%=p;
		b[sum]=i;
	}
	ll num=1ll;
	for(int i=0;i<=n;i++)
	{
		ll inv=quick(num,p-2);
		if(b[inv*a%p])
		{
			f=i*n+b[inv*a%p];
			break;
		}
		(num*=sum)%=p;
	}
	ll d=gcd(k,p-1);
	if(f%d)
	{
		printf("0");
		return 0;
	}
	f/=d;
	ll X=k/d;
	ll Y=(p-1)/d;
	ll x,y;
	exgcd(X,Y,x,y);
	(x*=f)%=(p-1);
	x%=Y;
	if(x<=0)
	{
		x+=Y;
	}
	while(x<=p-1)
	{
		q[++cnt]=quick(g,x);
		x+=Y;
	}
	sort(q+1,q+1+cnt);
	printf("%d\n",cnt);
	for(int i=1;i<=cnt;i++)
	{
		printf("%lld\n",q[i]);
	}
}

转载于:https://www.cnblogs.com/Khada-Jhin/p/10372227.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值