zoukankan      html  css  js  c++  java
  • 【BZOJ4869】相逢是问候(六省联考2017)-扩展欧拉定理+线段树

    测试地址:相逢是问候
    做法:本题需要用到扩展欧拉定理+线段树。
    我们知道在gcd(c,p)=1时,有欧拉定理:
    cxcx%φ(p)(modp)
    然而本题中c,p并不一定互质,那么我们有扩展欧拉定理:
    xφ(p)时,cxcx%φ(p)+φ(p)(modp)
    至于定理的证明,这里写不下(实际上是我不会),网上有很多大佬证过,可以去找一找。
    那么我们有了cx的求法,那ccx呢?
    注意到有ccxccx%φ(p)+φ(p)(modp)
    我们发现右边上面的cx%φ(p)又可以用扩展欧拉定理来算,于是如果我们知道这个东西嵌套几层,我们就可以递归去算了。
    观察到,模数随着层数的增加,每增加一层都取一次φ。有一个结论是,一个数p不断取φ,最多取logp+1次就会变成1。简单证明一下这个结论,我们知道求φ的过程就是先把该数质因数分解,然后对于每种质因子pi,把其中一个pi变成pi1。我们知道,除了2之外所有质数都是奇数,那么pi1就一定是偶数,所以新的数一定会多出一些质因子2,而与此同时,我们每次都把一个2变成1,也就是整个数除以2,至此我们证明了除了第一次外(因为一开始可能没有2),每次数字大小都会减少至少一半,所以结论得证。
    那么我们只需要知道在什么时候模数变成1,那么无论内层再怎么算,结果都是1,也就不再有影响了。于是我们用线段树维护每个数距离不变还剩的操作次数,如果一个区间内所有的数都不会再变,就不往下查找,否则暴力从每个点向根节点进行修改,时间复杂度为O(nlognlogp)(这个时间复杂度分析和区间取模的复杂度分析差不多啊…)。再加上每次用扩展欧拉定理修改,每次修改是O(log2p)的,那么总的时间复杂度为O(nlognlog3p),虽然常数要小很多,BZOJ上也可以过,但分点测试仍然会挂。
    注意到修改的O(log2p)的复杂度中,有一个log是因为扩展欧拉定理的递归,另一个是因为快速幂。扩展欧拉定理肯定没法优化了,所以我们从快速幂的角度进行优化。因为底数始终是c,我们可以令cx=c10000t+k,那么我们只需预处理出c10000tct两个表,即可做到O(1)询问。注意要对每种模数都做一次这个表,那么预处理复杂度为O(10000logp),前面的算法时间复杂度优化为O(nlognlog2p),可以飞快地通过此题。
    这题还有一个比较难考虑到的点:计算每个数距离不变还剩的操作次数时,不能简单的用pφ一直取到1的次数来做,而是应该+1,因为如果序列中存在0,那么例如当p=2时,它只需要1次取φ就能变成1,而当c=2时,0需要操作2次才能进入不变的状态,这显然就不对了。所以应该使用取的次数+1来作为最大操作次数。
    以下是本人代码:

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    int n,m,tot,mx[200010];
    ll p,c,a[50010],seg[200010],Phi[50],pwr[10010][50],pwrx[10010][50],low[50];
    
    ll phi(ll x)
    {
        ll s=x;
        for(ll i=2;i*i<=x;i++)
            if (x%i==0)
            {
                s=s/i*(i-1);
                while(x%i==0) x/=i;
            }
        if (x!=1) s=s/x*(x-1);
        return s;
    }
    
    void pushup(int no)
    {
        seg[no]=(seg[no<<1]+seg[no<<1|1])%p;
        mx[no]=max(mx[no<<1],mx[no<<1|1]);
    }
    
    void buildtree(int no,int l,int r)
    {
        if (l==r)
        {
            scanf("%lld",&a[l]);
            seg[no]=a[l];
            mx[no]=tot;
            return;
        }
        int mid=(l+r)>>1;
        buildtree(no<<1,l,mid);
        buildtree(no<<1|1,mid+1,r);
        pushup(no);
    }
    
    ll power(ll b,int p)
    {
        if (b<10000) return pwr[b][p];
        else return pwrx[b/10000][p]*pwr[b%10000][p]%Phi[p];
    }
    
    ll euler(ll x,ll t)
    {
        ll last,tmp=x;
        if (tmp>Phi[t]) tmp=tmp%Phi[t]+Phi[t];
        for(int i=t;i>0;i--)
        {
            last=tmp;
            tmp=power(tmp,i-1);
            if (last>=low[i-1]) tmp+=Phi[i-1];
        }
        return tmp;
    }
    
    void modify(int no,int l,int r,int s,int t)
    {
        if (!mx[no]) return;
        if (l==r)
        {
            mx[no]--;
            seg[no]=euler(a[l],tot-mx[no]);
            return;
        }
        int mid=(l+r)>>1;
        if (s<=mid&&mx[no<<1]) modify(no<<1,l,mid,s,t);
        if (t>mid&&mx[no<<1|1]) modify(no<<1|1,mid+1,r,s,t);
        pushup(no);
    }
    
    ll query(int no,int l,int r,int s,int t)
    {
        if (l>=s&&r<=t) return seg[no]%p;
        ll sum=0;
        int mid=(l+r)>>1;
        if (s<=mid) sum=(sum+query(no<<1,l,mid,s,t))%p;
        if (t>mid) sum=(sum+query(no<<1|1,mid+1,r,s,t))%p;
        return sum;
    }
    
    int main()
    {
        scanf("%d%d%lld%lld",&n,&m,&p,&c);
    
        tot=0;
        ll x=p;
        Phi[0]=x;
        while(x!=1)
        {
            x=phi(x);
            Phi[++tot]=x;
        }
        Phi[++tot]=1;
    
        for(int i=0;i<=tot;i++)
        {
            pwr[0][i]=1%Phi[i];
            low[i]=0;
            for(int j=1;j<=10000;j++)
            {
                pwr[j][i]=pwr[j-1][i]*c;
                if (pwr[j][i]>=Phi[i]&&!low[i]) low[i]=j;
                pwr[j][i]%=Phi[i];
            }
            pwrx[1][i]=pwr[10000][i];
            for(int j=2;j<=10000;j++)
                pwrx[j][i]=pwrx[j-1][i]*pwr[10000][i]%Phi[i];
        }
    
        buildtree(1,1,n);
        for(int i=1;i<=m;i++)
        {
            int op,l,r;
            scanf("%d%d%d",&op,&l,&r);
            if (!op) modify(1,1,n,l,r);
            else printf("%lld
    ",query(1,1,n,l,r));
        }
    
        return 0;
    }
  • 相关阅读:
    HttpServletRequest对象(转)
    HttpServletResponse对象(转)
    springboot+mybatis 用redis作二级缓存
    springboot+JPA 整合redis
    如何使用RedisTemplate访问Redis数据结构之Zset
    如何使用RedisTemplate访问Redis数据结构之Hash
    如何使用RedisTemplate访问Redis数据结构之list
    如何使用RedisTemplate访问Redis数据结构之字符串操作
    Spring Boot中使用Swagger2构建强大的RESTful API文档
    Centos之目录处理命令
  • 原文地址:https://www.cnblogs.com/Maxwei-wzj/p/9793369.html
Copyright © 2011-2022 走看看