【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\n",query(1,1,n,l,r));
    }

    return 0;
}
posted @ 2018-05-18 19:23  Maxwei_wzj  阅读(125)  评论(0编辑  收藏  举报