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

78 篇文章 0 订阅
16 篇文章 0 订阅

题目描述

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

题解

看模数那么奇怪找一下规律看看有没有奇怪的性质
发现每一个数立方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 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));
    }
}

总结

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值