【BZOJ4105】平方运算(THUSC2015)-线段树+找规律

测试地址:平方运算
题目大意:维护一个序列X,再给定一个模数p,要求支持两个操作:1.将一个区间内的Xi修改为Xi2modp。2.求i=lrXi
做法:本题需要用到线段树+找规律。
看到这种特别诡异的区间操作,不能直接用线段树维护,那肯定又要分析操作的性质了。注意到因为有一个模数p,所以平方很多次之后一定会进入一个循环,根据打表发现数据中所有的p都满足两个性质:
1.所有数操作至多不超过11步就会进入一个循环。
2.所有循环的循环节长度的LCM不超过60
于是对于操作区间中每一个没有进入循环的数,我们就暴力算出它的值,从它更新到根,这一个部分的总时间复杂度是O(11nlogn)。而对于一个区间,如果该区间内所有数都已经进入循环了,那么整个区间数字和也有循环,长度就是每个数所在循环的长度的LCM,前面我们已经得出这个数不超过60了。那么我们每次在向上更新时,应该计算出该区间节点在循环节内每个位置的和,这个可以根据它的左右子区间的信息合并,每更新一个节点的复杂度是O(60)。而在之后如果再更新到这整个区间,就可以O(1)修改每一个涉及到的整区间的信息了,因此这个部分的时间复杂度为O(60nlogn)。那么我们就完成了这一题。
以下是本人代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,m,loop[10010]={0},looplen[400010]={0},now[400010]={0},st[10010];
int len[10010]={0},vis[10010]={0},pos[10010]={0},tag[400010]={0};
ll p,a[100010],loopsum[400010][62],seg[400010]={0};

int gcd(int a,int b)
{
    return b?gcd(b,a%b):a;
}

int lcm(int a,int b)
{
    return a*b/gcd(a,b);
}

void pushdown(int no)
{
    if (tag[no])
    {
        tag[no<<1]+=tag[no];
        tag[no<<1|1]+=tag[no];
        seg[no<<1]=(seg[no<<1]+tag[no])%looplen[no<<1];
        seg[no<<1|1]=(seg[no<<1|1]+tag[no])%looplen[no<<1|1];
        tag[no]=0;
    }
}

void pushup(int no)
{
    seg[no]=0;
    if (!looplen[no<<1]) seg[no]+=seg[no<<1];
    else seg[no]+=loopsum[no<<1][seg[no<<1]];
    if (!looplen[no<<1|1]) seg[no]+=seg[no<<1|1];
    else seg[no]+=loopsum[no<<1|1][seg[no<<1|1]];
    if (looplen[no<<1]&&looplen[no<<1|1])
    {
        ll nowl=seg[no<<1],nowr=seg[no<<1|1];
        if (!looplen[no]) looplen[no]=lcm(looplen[no<<1],looplen[no<<1|1]);
        seg[no]=0;
        for(int i=0;i<looplen[no];i++)
        {
            loopsum[no][i]=loopsum[no<<1][nowl]+loopsum[no<<1|1][nowr];
            nowl=(nowl+1)%looplen[no<<1];
            nowr=(nowr+1)%looplen[no<<1|1];
        }
    }
}

void check(int no)
{
    if (!len[seg[no]])
    {
        looplen[no]=loop[seg[no]];
        for(int i=0;i<looplen[no];i++)
        {
            loopsum[no][i]=seg[no];
            seg[no]=seg[no]*seg[no]%p;
        }
        seg[no]=0;
    }
}

void buildtree(int no,int l,int r)
{
    if (l==r)
    {
        seg[no]=a[l];
        check(no);
        return;
    }
    int mid=(l+r)>>1;
    buildtree(no<<1,l,mid);
    buildtree(no<<1|1,mid+1,r);
    pushup(no);
}

void modify(int no,int l,int r,int s,int t)
{
    if (l>=s&&r<=t&&looplen[no])
    {
        tag[no]++;
        seg[no]=(seg[no]+1)%looplen[no];
        return;
    }
    if (l==r)
    {
        seg[no]=seg[no]*seg[no]%p;
        check(no);
        return;
    }
    int mid=(l+r)>>1;
    pushdown(no);
    if (s<=mid) modify(no<<1,l,mid,s,t);
    if (t>mid) 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)
    {
        if (!looplen[no]) return seg[no];
        else return loopsum[no][seg[no]];
    }
    int mid=(l+r)>>1;
    ll sum=0;
    pushdown(no);
    if (s<=mid) sum+=query(no<<1,l,mid,s,t);
    if (t>mid) sum+=query(no<<1|1,mid+1,r,s,t);
    return sum;
}

int main()
{
    scanf("%d%d%lld",&n,&m,&p);
    for(ll i=0;i<p;i++)
        if (!vis[i])
        {
            int x=i,top=0;
            while(!vis[x])
            {
                st[++top]=x;
                pos[x]=top;
                vis[x]=i+1;
                x=x*x%p;
            }
            if (vis[x]!=i+1)
            {
                for(int j=1;j<=top;j++)
                    len[st[j]]=top-j+1+len[x];
            }
            else
            {
                for(int j=1;j<pos[x];j++)
                    len[st[j]]=pos[x]-j;
                for(int j=pos[x];j<=top;j++)
                    loop[st[j]]=top-pos[x]+1;
            }
        }

    for(int i=1;i<=n;i++)
        scanf("%lld",&a[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-23 19:23  Maxwei_wzj  阅读(332)  评论(0编辑  收藏  举报