SNOI省选模拟赛Round4 T1 查询query 分块&&线段树

题目大意:给定一个序列,两种操作:

1)Q l r 询问[l,r]的和,对329701061取模。

2)C l r 将[l,r]内每个数变成其立方。

n,q<=100000。


题解:一眼线段树对吧。

听教练说是道简单的线段树,但我又傻又笨又手残,不知道咋维护每个数的立方,就去写分块了。

但是还能注意到一个问题:为啥这个模数看起来这么奇怪啊?肯定有某种奇异的规律,打表试试。

打了1~100的表后发现规律:一个数立方48次以后就会回到原数。

于是可以分块了。

先维护每个数立方1~48次各是多少,块内存这一块的和就行了。

先考虑询问:块外的暴力,块内的直接加。

但是修改操作会让这一段序列里的数立方次数不一样啊,我因此差点GG在考场上。

后来蹲坑时想到了一个乱搞的方法:每次修改完后我都把这个数的当前立方次数旋转到第一个,那么每次我只用看第一个就行了嘛,问题解决。

说起来可能有点绕,看代码应该能更清楚一点。

代码:

#include<bits/stdc++.h>
#define maxn 100005
#define K 48
#define mod 329701061
using namespace std;
typedef long long LL;
LL read()
{
	char c;LL sum=0,f=1;c=getchar();
	while(c<'0' || c>'9'){if(c=='-')f=-1;c=getchar();}
	while(c>='0' && c<='9'){sum=sum*10+c-'0';c=getchar();}
	return sum*f;
}
int n,q,m,tot;
int bel[maxn],l[maxn],r[maxn],cnt[maxn];
LL num[maxn][50],block[maxn][48];
LL temp[50];
void init()
{
    for(int i=1;i<=n;i++)
    for(int j=1;j<K;j++)
	num[i][j]=num[i][j-1]*num[i][j-1]%mod*num[i][j-1]%mod;
    for(int i=1;i<=tot;i++)
    for(int j=0;j<K;j++)
    for(int k=l[i];k<=r[i];k++)
    {
        block[i][j]+=num[k][j];
        if(block[i][j]>=mod)
		block[i][j]-=mod;
    }
}
void rotate(int id,int x)
{
    if(!x) return;
    for(int i=0;i<x;i++)temp[i]=num[id][i];
    for(int i=x;i<K;i++)num[id][i-x]=num[id][i];
    for(int i=0;i<x;i++)num[id][i+K-x]=temp[i];
}
void update(int b)
{
    for(int i=0;i<K;i++)
    {
        block[b][i]=0;
        for(int j=l[b];j<=r[b];j++)
        {
            block[b][i]+=num[j][i];
            if(block[b][i]>=mod)
			block[b][i]-=mod;
        }
    }
}
void add(int x,int y)
{
    if(bel[x]==bel[y])
    {
        for(int i=l[bel[x]];i<x;i++)
		rotate(i,cnt[bel[x]]);
        for(int i=y+1;i<=r[bel[y]];i++)
		rotate(i,cnt[bel[x]]);
        cnt[bel[x]]++;
		if(cnt[bel[x]]==K)
		cnt[bel[x]]=0;
        for(int i=x;i<=y;i++)
		rotate(i,cnt[bel[x]]);
        cnt[bel[x]]=0;
        update(bel[x]);
        return;
    }
    if(x==l[bel[x]]) x=bel[x];
    else
    {
        for(int i=l[bel[x]];i<x;i++)
		rotate(i,cnt[bel[x]]);
        cnt[bel[x]]++;
		if(cnt[bel[x]]==K)
		cnt[bel[x]]=0;
        for(int i=x;i<=r[bel[x]];i++)
		rotate(i,cnt[bel[x]]);
        cnt[bel[x]]=0;
        update(bel[x]);
        x=bel[x]+1;
    }
    if(y==r[bel[y]]) y=bel[y];
    else
    {
        for(int i=y+1;i<=r[bel[y]];i++)
		rotate(i,cnt[bel[y]]);
        cnt[bel[y]]++;
		if(cnt[bel[y]]==K)
		cnt[bel[y]]=0;
        for(int i=l[bel[y]];i<=y;i++)
		rotate(i,cnt[bel[y]]);
        cnt[bel[y]]=0;
        update(bel[y]);
        y=bel[y]-1;
    }
    for(int i=x;i<=y;i++)
    {
        cnt[i]++;
        if(cnt[i]==K)
		cnt[i]=0;
    }
}
LL query(int x,int y)
{
    LL ans=0;
    if(bel[x]==bel[y])
    {
        for(int i=x;i<=y;i++)
        {
            ans+=num[i][cnt[bel[x]]];
            if(ans>=mod) ans-=mod;
        }
        return ans;
    }
    if(x==l[bel[x]]) x=bel[x];
    else
    {
        for(int i=x;i<=r[bel[x]];i++)
        {
            ans+=num[i][cnt[bel[x]]];
            if(ans>=mod)
			ans-=mod;
        }
        x=bel[x]+1;
    }
    if(y==r[bel[y]])
	y=bel[y];
    else
    {
        for(int i=l[bel[y]];i<=y;i++)
        {
            ans+=num[i][cnt[bel[y]]];
            if(ans>=mod) ans-=mod;
        }
        y=bel[y]-1;
    }
    for(int i=x;i<=y;i++)
    {
        ans+=block[i][cnt[i]];
        if(ans>=mod)
		ans-=mod;
    }
    return ans;
}
int main()
{
    n=read();
    for (int i=1;i<=n;i++)
	num[i][0]=read();
    m=n/floor(sqrt(n*48));
    if(!m) m=1;
    tot=(n-1)/m+1;
    for(int i=1;i<=n;i++)
    {
        bel[i]=(i-1)/m+1;
        if(bel[i]!=bel[i-1])
		r[bel[i-1]]=i-1,l[bel[i]]=i;
    }
    r[tot]=n;
    init();
    q=read();
    while(q--)
    {
        char c=getchar();
        while(c!='C'&& c!='Q') c=getchar();
        int x=read(),y=read();
        if(x>y) swap(x,y);
        if(c=='C') add(x,y);
        if(c=='Q') printf("%lld\n",query(x,y));
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值