引入
一道签到题
初步分析
这是洛谷9月份月赛的一道题,作为一名蒟蒻,我当时做得真的是惨兮兮。
首先个
可以怎么表示呢?不难发现:
,故原式可化为
,即
;
再移项
;
这就是BSGS求解问题的一个标准形式:求最小非负整数,满足
。
BSGS算法
首先令,且
,其中
表示向上取整,即取大于某个数的最小整数。
这样原式就变成了:
,移项:
.
枚举,把
存入
表;
枚举,判断
是否在
表内,若在,可以证明满足条件的最小
值对应最小
,故直接输出
.
算法结束。
那么求解引入中的签到题,只需要把看成
,把
看成
,把
看成
即可,最终求得的
即为所求
值(当然也涉及到一些其它的东西,比如本题很坑的就是两数相乘会爆
,所以要用快速乘)。
那么为什么要取
呢,这其实需要用到费马小定理,本文不加以赘述,有兴趣的同学可以自己尝试证明。
注释
同余方程左右可以乘以同一个数或是同余的两个数;
同余方程左右可以加减同一个数或是同余的两个数;
向上取整有许多方法,对于c++而言,可以用
或者
;
由于
,
,故
越小,
越小;
快速乘是一种求解两数相乘取模,但乘的过程中会爆
的方法。
代码
#include<cstdio>
#include<cmath>
#include<map>
using namespace std;
long long k,m;
map<long long,long long>mp;
inline long long multi(long long x,long long y,long long mod) //快速乘
{
long long tmp=(x*y-(long long)(((long double)x*y+0.5)/mod)*mod);
if (tmp<0) return tmp+mod; else return tmp;
}
long long quickpower(long long a,int b)
{
long long t=1;
while (b>0)
{
if ((b&1)==1) t=multi(t,a,m);
if (b>1) a=multi(a,a,m);
b=b>>1;
}
return t;
}
int main()
{
mp.clear();
scanf("%lld%lld",&k,&m);
long long now=(9*k+1)%m;
mp[now]=0;
int mm=ceil(sqrt(m));
for (int i=1;i<=mm;i++) //预处理哈希表
{
now=multi(now,10,m);
mp[now]=i;
}
now=1;
long long q=quickpower(10,mm);
for (int i=1;i<=mm;i++)
{
now=multi(now,q,m);
if (mp[now])
{
printf("%lld",(((long long)(i)*(long long)(mm)-mp[now])%m+m)%m);
return 0;
}
}
return 0;
}