AtCoder Beginner Contest 335 (Sponsored by Mynavi) G. Discrete Logarithm Problems(群论的阶 拉格朗日定理)

文章讲述了如何使用试除法和拉格朗日定理求解一个关于质数p和一组数a[i]的问题,即寻找满足特定条件的(i,j)对的阶。通过计算每个数的阶并利用阶的性质,简化了问题到暴力遍历约数来解决。给出了一段C++代码实现该算法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目

n(n<=2e5)个数,第i个数ai(ai<p),给定质数p(2<=p<=1e13)

求存在正整数k,使得a_{i}^k\equiv a_{j}(mod\ p)成立的(i,j)(1<=i,j<=n)对数

思路来源

官方题解

heltion代码

题解

赛中搞了原根+bsgs一大堆,后来发现根本没必要

结论

称满足a^x\equiv 1(mod\ p),gcd(a,p)=1的最小正整数x为a的阶,记作ord_p(a)

ord_p(b)|ord_p(a),则方程a^k\equiv b(mod\ p)有解

拉格朗日定理

gcd(a,n)=1,有ord_n(a)|\varphi (n)

感性证明

4^6\equiv 3^3 \equiv 1(mod\ 13)\Leftrightarrow 4^2 \equiv 3 (mod\ 13)

感觉无需深究,当原根/群论的一个性质记就可以

方法论

试除法,找到每个ai的阶,

即如果除掉一个素因子x后还成立,就除掉一个素因子

得到每个数的阶后,1e13以内的数的约数最大1e4,但实际没有那么多

还有阶为x的恰有phi(x)个这样的限制

暴力n^2遍历约数即可,这里带了个map的log也过了

代码
#include<bits/stdc++.h>
using namespace std;
// #include<random>
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
#define per(i,a,b) for(int i=(a);i>=(b);--i)
typedef long long ll;
#define LL long long
typedef double db;
typedef pair<int,int> P;
#define fi first
#define se second
#define pb push_back
#define dbg(x) cerr<<(#x)<<":"<<x<<" ";
#define dbg2(x) cerr<<(#x)<<":"<<x<<endl;
#define SZ(a) (int)(a.size())
#define sci(a) scanf("%d",&(a))
#define pt(a) printf("%d",a);
#define pte(a) printf("%d\n",a)
#define ptlle(a) printf("%lld\n",a)
#define debug(...) fprintf(stderr, __VA_ARGS__)
typedef long long ll;
#define LL __int128
const int N=2e5+10;
ll p,a[N],d;
int n;
vector<ll>fac;
map<ll,ll>now;
ll modpow(ll x,ll n,ll mod){
    ll res=1;
    for(;n;n>>=1,x=(LL)x*x%mod){
        if(n&1)res=(LL)res*x%mod;
    }
    return res;
}
int main(){
    scanf("%d%lld",&n,&p);
    rep(i,1,n){
        scanf("%lld",&a[i]);
    }
    d=p-1;
    for(ll i=2;i*i<=d;++i){
        if(d%i==0){
            fac.pb(i);
            while(d%i==0)d/=i;
        }
    }
    if(d>1)fac.pb(d);
    rep(i,1,n){
        ll d=p-1;
        for(auto &x:fac){
            while(d%x==0 && modpow(a[i],d/x,p)==1){
                d/=x;
            }
        }
        now[d]++;
        //printf("i:%d d:%lld\n",i,d);
    }
    ll ans=0;
    for(auto &x:now){
        for(auto &y:now){
            if(x.fi%y.fi==0){
                ans+=1ll*x.se*y.se;
            }
        }
    }
    printf("%lld\n",ans);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小衣同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值