zoukankan      html  css  js  c++  java
  • 【BZOJ】2186 沙拉公主的困惑

    一道很有价值的题。

    【解析1】欧几里德算法求乘法逆元,前缀和

    [Analysis]O(T n log n)。



    [Sum]
    ①int运算。假设会超出界,第一个数前要加上(LL)即类型转换。
    ②gcd不变的欧几里德定理:能够是加。也能够是减。

    [Code]

    /**************************************************************
        Problem: 2186
        User: y20070316
        Language: C++
        Result: Accepted
        Time:6496 ms
        Memory:157056 kb
    ****************************************************************/
     
    #include <cstdio>
    #include <cstring>
    #include <cstdlib>
    using namespace std;
     
    typedef long long LL;
    const int N=10000001;
     
    int p[N],v[N],inv[N];   //screen
    int pre[N]; //Prefix n!
    int cas,r,n,m;  //Basic
    int x,y;    //Exgcd
     
    void exgcd(int i,int j)
    {
        if (!j) {x=1,y=0;return;}
        exgcd(j,i%j);
        int x_=y,y_=x-(i/j)*y;
        x=x_,y=y_;
    }
     
    int main(void)
    {
        scanf("%d%d",&cas,&r);
         
        for (int i=2;i<N;i++)
        {
            if (!v[i])
            {
                p[++p[0]]=i;
                exgcd(i,r),inv[i]=x%r;
            }
            for (int j=1;j<=p[0];j++)
            {
                if (i*p[j]>=N) break;
                v[i*p[j]]=1;
                if (i%p[j]==0) break;
            }
        }
         
        pre[0]=1;
        for (int i=1;i<N;i++)
            pre[i]=(LL)pre[i-1]*i%r;
         
        inv[1]=1;
        for (int i=2;i<N;i++)
            if (!inv[i])
                inv[i]=inv[i-1];
            else
            {
                inv[i]=(LL)inv[i]*(i-1)%r;
                inv[i]=(LL)inv[i]*inv[i-1]%r;
            }
         
        for (int cc=1;cc<=cas;cc++)
        {
            scanf("%d%d",&n,&m);
            printf("%d
    ",((LL)pre[n]*inv[m]%r+r)%r);
        }
         
        return 0;
    }</span>


    【解析2】递推求乘法逆元,前缀和

    [Analysis]O(Tn)
    性质:关于Mod M作用下i的逆元inv[i]=-(M/i)*inv[M%i]。
    证明:
    令a=M/i,b=M%i,
    ∴M=ai+b。
    ∴inv[i] = -a * inv[b]。


    同余式两边同一时候乘上i。得:
    i * inv[i]
    = -ai * inv[b]
    = (b-M) * inv[b]
    = b*inv[b]
    = 1 (mod M)
    ∴inv[i]为在Mod M下i的逆元,证毕。



    O(n)求法比直接求全部素数的逆元还慢一些...
    [Code]

    /**************************************************************
        Problem: 2186
        User: y20070316
        Language: C++
        Result: Accepted
        Time:7700 ms
        Memory:196116 kb
    ****************************************************************/
     
    #include <cstdio>
    #include <cstring>
    #include <cstdlib>
    using namespace std;
     
    typedef long long LL;
    const int N=10000001;
     
    int p[N],v[N];  //screen
    int inv[N],sinv[N]; //Mutiplicative Inverse
    int pre[N]; //Prefix n!
    int cas,r,n,m;  //Basic
     
    int main(void)
    {
        scanf("%d%d",&cas,&r);
         
        for (int i=2;i<N;i++)
        {
            if (!v[i]) p[++p[0]]=i;
            for (int j=1;j<=p[0];j++)
            {
                if (i*p[j]>=N) break;
                v[i*p[j]]=1;
                if (i%p[j]==0) break;
            }
        }
         
        pre[0]=1;
        for (int i=1;i<N;i++)
            pre[i]=(LL)pre[i-1]*i%r;
         
        inv[1]=1;
        for (int i=2;i<N;i++)
            inv[i]=(LL)(r-r/i)*inv[r%i]%r;
         
        sinv[1]=1;
        for (int i=2;i<N;i++)
        {
            sinv[i]=sinv[i-1];
            if (!v[i]) 
            {
                sinv[i]=(LL)sinv[i]*(i-1)%r;
                sinv[i]=(LL)sinv[i]*inv[i]%r;
            }
        }
         
        for (int cc=1;cc<=cas;cc++)
        {
            scanf("%d%d",&n,&m);
            printf("%d
    ",((LL)pre[n]*sinv[m]%r+r)%r);
        }
         
        return 0;
    }</span>


    以下是做这道题时做的一些笔记:

    1.阶乘的乘除
    ①直接计算。


    ②分解质因数。
    假设有取余,用①方便。
    假设要写高精度,用②方便。



    2、欧拉函数的求法
    例:求fai(60)
    ①分解质因数正规求法
    60=2^2 * 3^1 * 5^1。
    ∴fai(60)=60 * (1-1/2) * (1-1/3) * (1-1/5) = 16。
    ②依据①的还有一种求法
    fai(60)= (1 * 2^1) * (2*3^0) * (4*5^0)= 16。


    ③积性函数的解法:可结合欧拉筛法达到O(n)求出全部。


    fai(60) = fai(2^2) * fai(3^1) * fai(5^1) = 16。



    3、乘法逆元(mutiplicative inverse)
    (1)什么是乘法逆元?
    群G中随意一个元素a,都在G中有唯一的逆元a',s.t. aa'=a'a=e,e为单位元。


    例:求4关于1模7的逆元,即求关于X的方程 4X ≡1 (mod 7)。
    (2)怎么求乘法逆元?
    在求乘法逆元aa'=b(mod c)前,要满足(a,c)=1即(a,c)互质。
    ①同余方程 --> 不定方程 --> exgcd。

    单个。O(log n)。
    ②欧拉定理
    依据欧拉定理,当a与P互质时,a ^ fai(P) = 1 (mod P)。
    ∴a * a^(fai(P)-1) =1 (mod P)。
    在mod P意义下a的乘法逆元a' = a^(fai(P)-1)。


    特别的,当P为质数时,a' = a^(P-2)。

    单个,O(log fai(P)-1)。
    ③积性函数
    乘法逆元是积性函数,能够线性筛(screen)。


    对于素数考虑以上两种方法哪种好。

    全部。O(log n)或者O(fai(P)-1),一般来说用①。
    ④递推法。 全部,O(n)。
    关于递推法。见:
    http://blog.csdn.net/whyorwhnt/article/details/19169035。
    (3)一个经典的问题:求(a/b) mod p。


    性质:设b'为b的逆元,即:b'b=1(mod p),那么 a/b = a*b' (mod p)。


    证:
    ∵b'b=1(mod p)
    ∴b'b=1+px即b'=(1+px)/b。
    ∴a*b'=a*(1+px)/b=a*(1+0)/b(mod p)=a/b (mod p),证毕。


    这个性质在a变求和边取模,然后求(a/b) mod p时实用。

  • 相关阅读:
    [蓝桥杯2017初赛]青蛙跳杯子 BFS
    第十一章 进程和信号
    第七章 数据管理
    特殊符号大全
    第四章 Linux环境
    (十六)异常
    (十五)代理
    (十四)内部类
    第三章 文件操作
    (十三)对象克隆
  • 原文地址:https://www.cnblogs.com/lxjshuju/p/7227410.html
Copyright © 2011-2022 走看看