P4884 多少个1?

签到题爆零系列。。。

开局先说些有的没的。这道题是9月月赛的T1,题目名字叫做“签到题”,结果难度给了省选。。。真的毒瘤!


题面就是让你求\(11\cdots1111(N个1) \equiv K \pmod m\)。其中他说\(m\)是质数。

暴力模拟一分都拿不到!很气!

下面我们来看正解。。。

其实这\(N个1\)是可以有通项公式的。数学必修五学到这个公式。\(a_n=\frac{10^n-1}{9}\)

所以那个同余的式子就能变成\(\frac{10^n-1}{9} \equiv K \pmod m\)

同余的等号也有类似于一般的等号。我们可以左右各乘以9再加1。结果就变成了\(10^n \equiv 9K+1 \pmod m\)。这种求指数的同余方程叫做什么离散对数。

接下来介绍一个名字叫做大步小步算法(baby-step giant-step algorithm)。这个算法就是专门来解决这类问题的。

我们考虑一个一般性的方程\(y^x \equiv z \pmod p\)

温馨提示:下面的东西有点玄学。。。

我们设\(m=\lceil \sqrt p \rceil\),令我们要求的\(x=a \times m-b\)。这里很巧妙设为减,后面有大作用。

所以变成了\(y^{a \times m - b} \equiv z \pmod p\),移项得\(y^{a \times m} \equiv z \times y^b \pmod p\)

这里注意下取值范围:上面我们定义了两个新的变量\(a和b\)\(a \in (0,m+1],b \in [0,m)\)

我们先算出所有的\(z \times y^b\)的值,然后用一个hash表记录下来,我们就能用值来取出指数\(b\)。这里应该是baby-step部分。

处理完右边,我们可以处理左边了。这里把\(y^{a\times m}\)变成\({y^m}^a\)。这里是giant-step部分:弄出一个变量giant\(y^m\)。然后我们套\(a\)次的循环,每一次循环算出左边的模值,看看hash表里面有没有,如果有的话你就找到解了,解的值为\(i \times m - b\)返回掉。

但是这个方程也可能是无解的。我们拿-1来作为无解的标志就行了。

差不多就是这样了。看起来算法思想很玄学,但是实现起来还可以。

对了,这个算法有前置技能:快速乘和快速幂。一个使用乘法分配律,用加的,另一个用指数的性质,用乘的。

注意:快速幂的乘可以用快速乘来优化。所谓的快速乘就是来防止你乘完中间过程还是什么的爆long long。

快速乘有\(O(logn)\)的,也有\(O(1)\)的。这里卡log的快速乘。。。真的毒瘤!

代码:

#include<cstdio>
#include<cmath>
#include<map>

#define ll long long
ll k, mod;

ll mul_mod(ll a, ll b)
{
    ll ans = 0, base = a;
    while(b)
    {
        if(b & 1) ans = (ans + base) % mod;
        base = (base + base) % mod;
        b >>= 1;
    }
    return ans % mod;
}
ll mult(ll a,ll b){
      auto ans=a*b-(ll)((long double)a/mod*b+1e-10)*mod;
      return (ans+mod)%mod;
}
ll pow_mod(ll a, ll b)
{
    ll ans = 1, base = a;
    while(b)
    {
        if(b & 1) ans = mult(ans, base);
        base = mult(base, base);
        b >>= 1;
    }
    return ans % mod;
}
ll bsgs(ll a, ll b)
{
    // a^x === b (mod mod)
    // x = a * m - b
    // a^(a * m - b) === b (mod mod)
    // a^(a * m) === b * a^b (mod mod)
    if(a == 0)
    {
        if(b == 0) return 1;
        else return -1;
    }
    std::map<ll, ll> hash;
    b = b % mod;
    ll m = sqrt(mod) + 1;
    for(int i = 0; i < m; i++)
    {
        ll val = mult(b, pow_mod(a, i));
        if(!hash.count(val)) hash[val] = i;
    }
    ll giant = pow_mod(a, m);
    for(int i = 0; i <= m; i++)
    {
        ll val = pow_mod(giant, i);
        ll j = hash.find(val) == hash.end() ? -1 : hash[val];
        if(j >= 0 && i * m - j >= 0) return i * m - j;
    }
    return -1;
}
int main()
{
    scanf("%lld%lld", &k, &mod);
    ll ans = bsgs(10, 9 * k + 1);
    printf("%lld\n", ans);
    return 0;
}

转载于:https://www.cnblogs.com/Garen-Wang/p/9694455.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值