省选算法学习-BSGS与exBSGS

前置知识

扩展欧几里得,快速幂

都是很基础的东西

扩展欧几里得

说实话这个东西我学了好几遍都没有懂,最近终于搞明白,可以考场现推了,故放到这里来加深印象

翡蜀定理

方程$ax+by=gcd(a,b)$一定有整数解

证明:

因为$gcd(a,b)=gcd(b,a$ $mod$ $b)$

所以假设我们已经求出来了$bx+(a$ $mod$ $b)y=gcd(b,a$ $mod$ $b)$的一组整数解$(p,q)$

因为$a$ $mod$ $b=a-(\lfloor \frac{a}{b} \rfloor \ast b)$

所以$bp+(a-a/b\ast b)q=ax+by$

$b(p-(a/b)q)+aq=ax+by$

所以$x=q,y=(p-(a/b)q)$是一组合法的解

所以我们可以递归$gcd$的过程中倒着算每一层的解

当$b=0$时的解为$x=1,y=0$

BSGS

问题提出

给定$a,b,p$,求最小的$x$,使得$a^x≡b(mod$ $p)$

问题求解

显然这个东西不能直接做

考虑分块的思想

定义$m=sqrt(p)$

设$x=i\ast m - j$

也就是$a^{i\ast m}≡a^j\ast b(mod$ $p)$

那么我们首先把$j=0...m-1$时的$a^j\ast b$插入一个哈希表

然后我们枚举$i$,在哈希表里面查询$a^{i\ast m}$有没有出现过,如果出现过,它最大的$j$是多少

然后就可以在$O(sqrt(p))$的时间内解决这个问题了

放个板子


namespace hash{
    ll first[1000010],next[1000010],val[1000010],hash[1000010],mod=926081,cnt=0;
    void init(){memset(first,0,sizeof(first));cnt=0;}
    void insert(ll w,ll pos){
        ll p=w%mod,u;
        for(u=first[p];u;u=next[u]){
            if(hash[u]==w){val[u]=pos;return;}
            if(!next[u]) break;
        }
        if(!next[u]){
            cnt++;
            if(!first[p]) first[p]=cnt;
            else next[u]=cnt;
            val[cnt]=pos;hash[cnt]=w;next[cnt]=0;
        }
    }
    ll find(ll w){
        ll p=w%mod,u;
        for(u=first[p];u;u=next[u]){
            if(hash[u]==w) return val[u];
        }
        return -1;
    }
}
ll qpow(ll a,ll b,ll p){
    ll re=1;
    while(b){
        if(b&1) re=re*a%p;
        a=a*a%p;b>>=1;
    }
    return re;
}
ll gcd(ll a,ll b){
    if(b==0) return a;
    return gcd(b,a%b);
}
ll bsgs(ll a,ll b,ll p){
    if(b==1) return 0;
    ll i,j,m=ceil(sqrt((double)p)),tmp=b,cur,base=qpow(a,m,p);
    hash::init();
    for(j=0;j<m;j++){
        hash::insert(tmp,j);
        tmp=tmp*a%p;
    }
    tmp=1;
    for(i=m;i<=p;i+=m){
        tmp=tmp*base%p;
                cur=hash::find(tmp);
        if(~cur) return i-cur;
    }
    return -1;
}

exBSGS

使用BSGS的时候要求$gcd(a,p)=1$,扩展版的exBSGS则不需要

具体操作是这样的:

除掉公约数

假设$tmp=gcd(a,p)$

那么$(y\ast tmp)^x=z\ast tmp(mod$ $q\ast tmp)$

其中$y,z,q$是新设出来的量,$y\ast tmp=a$,$z\ast tmp=b$,$q\ast tmp=p$

这一步可以看出,如果$b$不能整除$gcd(a,p)$,那么一定无解

转化

把等式两边的含$tmp$的东西提取出来,可以得到:

$y^{tmp}=z(mod$ $q)$

然后就可以继续递归下去处理了

代码


namespace hash{
    ll first[1000010],val[1000010],hash[1000010],next[1000010],cnt=0,mod=926081;
    void init(){memset(first,0,sizeof(first));cnt=0;}
    void insert(ll w,ll pos){
        ll p=w%mod,u;
        for(u=first[p];u;u=next[u]){
            if(hash[u]==w){val[u]=pos;return;}
            if(!next[u]) break;
        }
        if(!next[u]){
            cnt++;
            if(!first[p]) first[p]=cnt;
            else next[u]=cnt;
            next[cnt]=0;val[cnt]=pos;hash[cnt]=w;
        }
    }
    ll query(ll w){
        ll p=w%mod,u;
        for(u=first[p];u;u=next[u]){
            if(hash[u]==w) return val[u];
        }
        return -1;
    }
}
ll qpow(ll a,ll b,ll p){
    ll re=1;
    while(b){
        if(b&1) re=re*a%p;
        a=a*a%p;b>>=1;
    }
    return re;
}
ll gcd(ll a,ll b){
    if(b==0) return a;
    else return gcd(b,a%b);
}
ll bsgs(ll a,ll b,ll p){
    if(b==1) return 0;//不要忘了特判
    ll i,j,tmp=1,d=1,cnt=0;
    hash::init();
    while((tmp=gcd(a,p))!=1){
        if(b%tmp) return -1;
        cnt++;b/=tmp;p/=tmp;d=d*(a/tmp)%p;//注意这个d的写法
        if(b==d) return cnt;//记得写这个
    }
    ll m=ceil(sqrt(double(p))),base=qpow(a,m,p);//注意这两个东西一定要写在这里,不要写在while上面
    tmp=b;
    for(j=0;j<m;j++){
        hash::insert(tmp,j);
        tmp=(tmp*a)%p;
    }
    for(i=m;i<=p+m;i+=m){//这里注意p+m,不然的话可能会有少数情况挂掉
        d=(d*base)%p;//同时注意这里的tmp相当于是一开始就是上面的d而不是1,也就是一开始要乘上已经除掉的东西
        tmp=hash::query(d);
        if(tmp!=-1) return i-tmp+cnt;
    }
    return -1;
}

原文地址:https://www.cnblogs.com/dedicatus545/p/10160883.html

时间: 2024-10-09 22:34:55

省选算法学习-BSGS与exBSGS的相关文章

省选算法学习-数据结构-splay

于是乎,在丧心病狂的noip2017结束之后,我们很快就要迎来更加丧心病狂的省选了-_-|| 所以从写完上一篇博客开始到现在我一直深陷数据结构和网络流的漩涡不能自拔 今天终于想起来写博客(只是懒吧......) 言归正传. 省选级别的数据结构比NOIP要高到不知道哪里去了. noip只考一点线段树啊st表啊并查集啊之类的简单数据结构,而且应用范围很窄 但是省选里面对数据结构,尤其是高级数据结构的要求就高了很多,更有一些题目看着就是数据结构题,也没有别的做法. 因此掌握高级数据结构就成了准备省选的

省选算法学习-矩阵与矩阵快速幂

0x00 引入 矩阵,顾名思义,就是由数构成的矩形阵列 比如这样的:$\begin{array}{l}\begin{bmatrix}2&3&4\0&7&13\c&\alpha&\sqrt5\end{bmatrix}\\end{array}$ 就是一个3*3的矩阵 矩阵在信息学乃至数学里面的用处都非常广泛,下面就来介绍下它的一些基本知识,以及常用的地方.本文同时还会介绍矩阵快速幂以及快速矩阵乘法. 0x01 何为矩阵 矩阵的定义 其实就是上面那样的啦.....

BSGS算法学习小记(大步小步算法)

简介 先看一个式子xy≡z(modp),z是质数 现在只知道x和z,要求y. 大步小步算法(BSGS,Baby Steps Giant Steps)就是解决这个问题. 算法流程 暴搜的枚举范围 根据费马小定理:xz?1≡1. 如果y已经枚举到了z-1了,继续枚举的话就会产生循环. 所以,在暴搜中y的枚举范围就是0--z-1. 如何优化暴搜 我们想一想可不可以用分块来解决枚举的y. 把y分成p?1????√分别枚举行不行? 设m=p?1????√,y=a?m+b,这样枚举a和b就相当于分块枚举了.

我的算法学习(一)----数组的全排列

看见别人写出来美丽有用的代码,最终下定决心好好学习算法,在这里记录下自己学习的成果. 前两天看到数组的全排列,于是自己照着别人的想法实现了一下,感觉自己理解了,有点小高兴,记载一下. /* * 数组的全排列*/ public class myAllSort { public static void sort(int[] number,int start,int end){ int temp; // 假设发现数组对掉元素到了最后一个,那么就输出,证明已经符合要求 if(start==end) {

算法学习 - 表达树的建立(后缀表达式法),树的先序遍历,中序遍历,后序遍历

表达树就是根据后缀表达式来建立一个二叉树. 这个二叉树的每个叶子节点就是数,真祖先都是操作符. 通过栈来建立的,所以这里也会有很多栈的操作. 树的先序遍历,中序遍历,后序遍历的概念我就不讲了,不会的自行百度,不然也看不懂我的代码. 下面是代码: // // main.cpp // expressionTree // // Created by Alps on 14-7-29. // Copyright (c) 2014年 chen. All rights reserved. // #includ

我的算法学习之路

关于 严格来说,本文题目应该是我的数据结构和算法学习之路,但这个写法实在太绕口--况且CS中的算法往往暗指数据结构和算法(例如算法导论指的实际上是数据结构和算法导论),所以我认为本文题目是合理的. 这篇文章讲了什么? 我这些年学习数据结构和算法的总结. 一些不错的算法书籍和教程. 算法的重要性. 初学 第一次接触数据结构是在大二下学期的数据结构课程.然而这门课程并没有让我入门--当时自己正忙于倒卖各种MP3和耳机,对于这些课程根本就不屑一顾--反正最后考试划个重点也能过,于是这门整个计算机专业本

算法学习三阶段

?? 第一阶段:练经典经常使用算法,以下的每一个算法给我打上十到二十遍,同一时候自己精简代码, 由于太经常使用,所以要练到写时不用想,10-15分钟内打完,甚至关掉显示器都能够把程序打 出来. 1.最短路(Floyd.Dijstra,BellmanFord) 2.最小生成树(先写个prim,kruscal 要用并查集,不好写) 3.大数(高精度)加减乘除 4.二分查找. (代码可在五行以内) 5.叉乘.判线段相交.然后写个凸包. 6.BFS.DFS,同一时候熟练hash 表(要熟,要灵活,代码要

周总结(2017.2.16):第一周算法学习。

周总结:算法学习总结之DFS和BFS 一:DFS算法 目的:达到被搜索结构的叶节点. 定义:假定给定图G的初态是所有的定点都没有访问过,在G中任选一定点V为初始出发点,首先访问出发点并标记,然后依次从V出发搜索V的每个相邻点W,若W未曾出现过,则对W进行深度优先遍历(DFS),知道所有和V有路径相通的定点被访问. 如果从V0开始寻找一条长度为4的路径的话: 思路步骤: 先寻找V0的所有相邻点:dis{v1,v2,v3},V1没有访问过,所以对V1进行深度遍历并将V1标记为访问过,此时路径长度为1

算法学习 - 01背包问题(动态规划C++)

动态规划 01背包 问题描述 求解思路 代码实现 放入哪些物品 代码 动态规划 我在上一篇博客里已经讲了一点动态规划了,传送门:算法学习 - 动态规划(DP问题)(C++) 这里说一下,遇到动态规划应该如何去想,才能找到解决办法. 最主要的其实是要找状态转移的方程,例如上一篇博客里面,找的就是当前两条生产线的第i个station的最短时间和上一时刻的时间关系. minTime(station[1][i]) = minTime(station[1][i-1] + time[i], station[