题目描述
题解
看模数那么奇怪找一下规律看看有没有奇怪的性质
发现每一个数立方48次后回到原数
线段树不如分块好写
维护每个数立方k次后得到的数,每一个块所有的数分别立方k次后的和
修改时,对于整块记录立方的次数,其余的暴力重构
重构即把块内的点维护的立方旋转t次,然后重新计算和
查询时,整块直接查询,其余暴力
时间复杂度
O(m(block+n∗szblock))
,可以发现当
block=n∗sz−−−−−√
时有最小值,
O(mn∗sz−−−−−√)≈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 100005
char 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));
}
}
总结
卡常数技巧
①分块大小
nn∗sz√
②加法的取模改成加减
③尽量不用乘法用加减