[SD2014集训]查询(分块+数学相关)

来源:互联网 发布:centos7.0安装nginx 编辑:程序博客网 时间:2024/05/29 14:13

题目描述

这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述

题解

看模数那么奇怪找一下规律看看有没有奇怪的性质
发现每一个数立方48次后回到原数
线段树不如分块好写
维护每个数立方k次后得到的数,每一个块所有的数分别立方k次后的和
修改时,对于整块记录立方的次数,其余的暴力重构
重构即把块内的点维护的立方旋转t次,然后重新计算和
查询时,整块直接查询,其余暴力
时间复杂度O(m(block+nszblock)),可以发现当block=nsz时有最小值,O(mnsz)2.19s

代码

#include<algorithm>#include<iostream>#include<cstring>#include<cstdio>#include<cmath>using namespace std;#define LL long long#define Mod 329701061#define sz 48#define N 100005char opt;int n,m,x,y,block,tot;int t[N],num[N],l[N],r[N];LL a[N],cub[N][sz],Cub[N][sz],s[sz];void init(){    for (int i=1;i<=n;++i)    {        cub[i][0]=a[i];        for (int j=1;j<sz;++j) cub[i][j]=cub[i][j-1]*cub[i][j-1]%Mod*cub[i][j-1]%Mod;    }    for (int i=1;i<=tot;++i)        for (int j=0;j<sz;++j)            for (int k=l[i];k<=r[i];++k)            {                Cub[i][j]+=cub[k][j];                if (Cub[i][j]>=Mod) Cub[i][j]-=Mod;            }}void move(int id,int rad){    if (!rad) return;    for (int i=0;i<rad;++i) s[i]=cub[id][i];    for (int i=rad;i<sz;++i) cub[id][i-rad]=cub[id][i];    for (int i=0;i<rad;++i) cub[id][i+sz-rad]=s[i];}void calc(int id){    for (int i=0;i<sz;++i)    {        Cub[id][i]=0;        for (int j=l[id];j<=r[id];++j)        {            Cub[id][i]+=cub[j][i];            if (Cub[id][i]>=Mod) Cub[id][i]-=Mod;        }    }}void change(int x,int y){    if (num[x]==num[y])    {        for (int i=l[num[x]];i<x;++i) move(i,t[num[x]]);        for (int i=y+1;i<=r[num[y]];++i) move(i,t[num[x]]);        ++t[num[x]];if (t[num[x]]==sz) t[num[x]]=0;        for (int i=x;i<=y;++i) move(i,t[num[x]]);        t[num[x]]=0;        calc(num[x]);        return;    }    if (x==l[num[x]]) x=num[x];    else    {        for (int i=l[num[x]];i<x;++i) move(i,t[num[x]]);        ++t[num[x]];if (t[num[x]]==sz) t[num[x]]=0;        for (int i=x;i<=r[num[x]];++i) move(i,t[num[x]]);        t[num[x]]=0;        calc(num[x]);        x=num[x]+1;    }    if (y==r[num[y]]) y=num[y];    else    {        for (int i=y+1;i<=r[num[y]];++i) move(i,t[num[y]]);        ++t[num[y]];if (t[num[y]]==sz) t[num[y]]=0;        for (int i=l[num[y]];i<=y;++i) move(i,t[num[y]]);        t[num[y]]=0;        calc(num[y]);        y=num[y]-1;    }    for (int i=x;i<=y;++i)    {        ++t[i];        if (t[i]==sz) t[i]=0;    }}LL query(int x,int y){    LL ans=0;    if (num[x]==num[y])    {        for (int i=x;i<=y;++i)        {            ans+=cub[i][t[num[x]]];            if (ans>=Mod) ans-=Mod;        }        return ans;    }    if (x==l[num[x]]) x=num[x];    else    {        for (int i=x;i<=r[num[x]];++i)        {            ans+=cub[i][t[num[x]]];            if (ans>=Mod) ans-=Mod;        }        x=num[x]+1;    }    if (y==r[num[y]]) y=num[y];    else    {        for (int i=l[num[y]];i<=y;++i)        {            ans+=cub[i][t[num[y]]];            if (ans>=Mod) ans-=Mod;        }        y=num[y]-1;    }    for (int i=x;i<=y;++i)    {        ans+=Cub[i][t[i]];        if (ans>=Mod) ans-=Mod;    }    return ans;}int main(){    scanf("%d",&n);    for (int i=1;i<=n;++i) scanf("%I64d",&a[i]);    block=n/floor(sqrt(n*sz));    if (!block) ++block;    tot=(n-1)/block+1;    for (int i=1;i<=n;++i)    {        num[i]=(i-1)/block+1;        if (num[i]!=num[i-1]) r[num[i-1]]=i-1,l[num[i]]=i;    }    r[tot]=n;    init();    scanf("%d",&m);    for (int i=1;i<=m;++i)    {        opt=getchar();        while (opt!='C'&&opt!='Q') opt=getchar();        scanf("%d%d",&x,&y);        if (x>y) swap(x,y);        if (opt=='C') change(x,y);        else printf("%I64d\n",query(x,y));    }}

总结

卡常数技巧
①分块大小nnsz
②加法的取模改成加减
③尽量不用乘法用加减

0 0
原创粉丝点击