【数列分块入门】1~9部分题解

-----------------------一起跃入人海,做一朵奔涌的浪花---------------------------

欢迎光临小店

        • 问题 A: 数列分块入门1
        • 问题 B: 数列分块入门 2
        • 问题 C: 数列分块入门 3(待补)
        • 问题 D: 数列分块入门 4(树状数组的操作)
        • 问题 E: 数列分块入门 5
        • 问题 F: 数列分块入门 6
        • 问题 G: 数列分块入门 7
        • 问题 H: 数列分块入门 8(待补)
        • 问题 I: 数列分块入门 9(待补)

店面不大,请多包涵,哈哈哈哈哈

问题 A: 数列分块入门1

时间限制: 1 S e c 1 Sec 1Sec 内存限制: 256 M B 256 MB 256MB
题目描述
给出一个长为 n n n的数列,以及 n n n 个操作,操作涉及区间加法,单点查值
输入
第一行输入一个数字 n n n
第二行输入 n n n个数字,第i个数字为 a i ai ai,以空格隔开。
接下来输入 n n n行询问,每行输入四个数字 o p t opt opt l l l r r r c c c,以空格隔开。
o p t = 0 opt=0 opt=0,表示将位于 [ l , r ] [l,r] [l,r]的之间的数字都加c。
o p t = 1 opt=1 opt=1,表示询问 a r ar ar 的值( l l l c c c忽略)。

输出
对于每次询问,输出一行一个数字表示答案。

样例输入

4
1 2 2 3
0 1 3 1
1 0 1 0
0 1 2 2
1 0 2 0

样例输出

2
5

提示
n < = 50000 , − 2 31 < = o t h e r s , a n s < = 2 31 − 1 n<=50000,-2^{31}<=others, ans <=2^{31}-1 n<=50000,231<=others,ans<=2311

解题思路:首先是 a d d add add操作,将 [ l , r ] [l,r] [l,r]之间的元素都加 c c c,对于整块的内容来说,直接维护一个 l a z y lazy lazy标记即可, l a z y lazy lazy标记就记录这个整块中被 a d d add add了多少(注意不是记录加了多少次,是记录该整块中所有加的值的总和);而对于非整块来说,因为块内的元素并不是太多,暴力也是可以。接着就是 q u a r y quary quary操作,这里比较简单的就是,它是个单点查询,故查询函数的返回值就是 a [ r ] + ( a [ r ] a[r]+(a[r] a[r]+(a[r]所在块中的 l a z y lazy lazy值)。

预处理和维护内容:先要把n给分成各个小块,经由其他大佬们的分析,分成每个块中最多放 s q r t ( n ) sqrt(n) sqrt(n)个元素的时间复杂是最小的,在这里我们就不必再多考虑了直接拿来用就是。用 b l o n g [ i ] blong[i] blong[i]来记录原数组第i个元素在第几个块中; L [ x ] L[x] L[x]记录第 x x x个块的左边界, R [ x ] R[x] R[x]记录第 x x x个块的右边界;当然也少不了去维护 l a z y lazy lazy数组啦, l a z y [ x ] lazy[x] lazy[x]记录的就是第 x x x块被 a d d add add了多少的值。

好,分析完毕,默默说一句,这道题用树状数组貌似是更快一点的。不过为了学习分块最好是用分块再来做一遍,当作入门中的入门,哈哈哈

上代码:

#include
using namespace std;
typedef long long ll;
const int N=50010;
int n,m,block,L[N],R[N],blong[N],num;
ll a[N],lazy[N];
/*
a[i]原数列元素
block块的大小
num块的个数 
blong[i]表示属于哪一块
L[i]表示第i块的左边界
R[i]表示第i块的右边界 
lazy[i]对第i块的懒惰标记 
*/
void build()
{
	block=sqrt(n);//每个块的大小,sqrt(n)时复杂度最低 
	if(n%block==0) num = n/block;
	else num = n/block+1;  //最后一个快可能不够block个 
	for(int i=1;i<=num;i++)
	{
		L[i]=(i-1)*block+1,R[i]=i*block;//每个块的左右边界	
	} 
	R[num] = n; //最后一块特殊处理
	for(int i=1;i<=n;i++)
	{
		blong[i]=(i-1)/block + 1;//块的个数从1开始,So不应写成i/block 
	}
	 
}
void add(int l,int r,ll c )
{
	if(blong[l]==blong[r])//在同一块中,直接暴力处理 
	{
		for(int i=l;i<=r;i++)
		{
			a[i]+=c;
		}
		return; 
	}
	for(int i=l;i<=R[blong[l]];i++)//左侧不完整快 
	{
		a[i]+=c;
	}
	for(int i=blong[l]+1;i<=blong[r]-1;i++)//处理中间的完整快 
	{
		lazy[i] += c; 
	} 
	for(int i=L[blong[r]];i<=r;i++)//右侧不完整块 
	{
		a[i]+=c;
	} 
} 
ll quary(int x)
{
	return a[x]+lazy[blong[x]];
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
	build();
	m=n;
	while(m--)
	{
		int op,l,r;
		ll c;
		scanf("%d%d%d%lld",&op,&l,&r,&c);
		if(op==0)
		{
			add(l,r,c);
		}
		else{
			printf("%lld\n",quary(r));
		}
	}
	return 0;
}

问题 B: 数列分块入门 2

时间限制: 3 S e c 3 Sec 3Sec 内存限制: 256 M B 256 MB 256MB

题目描述
给出一个长为 n n n 的数列,以及 n n n 个操作,操作涉及区间加法,询问区间内小于某个值 x x x 的元素个数。

输入
第一行输入一个数字 n n n
第二行输入 n n n 个数字,第 i i i 个数字为 a i ai ai,以空格隔开。
接下来输入 n n n 行询问,每行输入四个数字 o p t opt opt l l l r r r c c c,以空格隔开。
o p t = 0 opt=0 opt=0,表示将位于 [ l , r ] [l,r] [l,r]的之间的数字都加 c c c
o p t = 1 opt=1 opt=1,表示询问中 [ l , r ] [l,r] [l,r]小于 c 2 c^2 c2的数字的个数。

输出
对于每次询问,输出一行一个数字表示答案。

样例输入

4
1 2 2 3
0 1 3 1
1 1 3 2
1 1 4 1
1 2 3 2

样例输出

3
0
2

提示
n < = 50000 , − 2 31 < = o t h e r s , a n s < = 2 31 − 1 n<=50000, -2^{31}<=others, ans<=2^{31}-1 n<=50000,231<=others,ans<=2311

解题思路:
首先我们来分析 q u a r y quary quary这个操作,我们查询的是一个区间内小于 c 2 c^2 c2的元素个数,对于非整块来说就是暴力查询即可,但对于整块来说,我们怎样才能更快的知道有多少个元素小于 c 2 c^2 c2呢?在这里我们来分析一下:如果这个整块是无序的,那我们就只能是暴力一个一个的判断是否符合条件,显然,在这么大的数据中,暴力是不可能让你过的;好,再来想一想,如果这个块是有序的,是不是就可以更快一点的得到答案呢,是可以的,有序后我们就可以用二分,二分的效率也是很高呐,正解也确实就是二分,这样的话, q u a r y quary quary就给分析完毕了。

接着,来分析 a d d add add操作,和第一题的是相差不多的,唯独就是多了个给 q u a r y quary quary函数便捷的小通道,加了一个很好用 v e c t o r vector vector容器,作用是什么呢,那当然是对某个块进行排顺序所需咯。。(还有就是 S T L STL STL里的 l o w e r lower lower_ b o u n d bound bound函数可是要比手写二分简短的多呢)。。在我们更改序列中的元素后,对于整块的来说同时加上相同的元素,相对大小是不变的,也就是说序列顺序未曾改变;但对于非整块的来说,相当于在整块中把一部分元素给增加了,另一部分元素不变,此时这个块儿也就变为无序了。为了方便 q u a r y quary quary的进行,我们就需要再对这个块进行重新排序操作,至此, a d d add add函数差不多也都分析完了。

知晓上方我们想要的操作后,需要预处理的除了和第一题相同的 L [ x ] , R [ x ] , l a z y [ x ] , b l o n g [ i ] L[x],R[x],lazy[x],blong[i] L[x],R[x],lazy[x],blong[i]之外,我们需要把每一个快中的元素放入容器中,且在一开始就把已经放入容器中的元素进行排序。

OK,就这样,感觉我太罗嗦了,下方代码

上代码:

#include
using namespace std;
typedef long long ll;
const int N=50010;
int n,m,L[N],R[N],block,num,blong[N];
ll a[N],lazy[N];
vector<ll> v[N];
void build()
{
    block=sqrt(n);//每个块的大小,sqrt(n)时复杂度最低 
    if(n%block==0) num = n/block;
    else num = n/block + 1;  //最后一个块可能不够block个 
    for(int i=1;i<=n;i++)
    {
        blong[i]=(i-1)/block + 1;//块的个数从1开始,So不应写成i/block 
        v[blong[i]].push_back(a[i]);
    }
    for(int i=1;i<=num;i++)
    {
        L[i]=(i-1)*block+1,R[i]=i*block;//每个块的左右边界
    } 
    R[num] = n; //最后一块特殊处理  
    for(int i=1;i<=num;i++)
    {
        sort(v[i].begin(),v[i].end());
    }
}
void resort(int x)//对x块进行排序
{
    v[x].clear();
    for(int i=L[x];i<=R[x];i++)
    {
        v[x].push_back(a[i]);   
    } 
    sort(v[x].begin(),v[x].end());
} 
void update(int l,int r,ll c)
{
    if(blong[l]==blong[r])
    {
        for(int i=l;i<=r;i++)
        {
            a[i]+=c;
        }
        resort(blong[l]);
        return;
    }
    for(int i=l;i<=R[blong[l]];i++)
    {
        a[i]+=c;
    }
    for(int i=blong[l]+1;i<=blong[r]-1;i++)
    {
        lazy[i]+=c;
    }
    for(int i=L[blong[r]];i<=r;i++)
    {
        a[i]+=c;
    }
    resort(blong[l]);
    resort(blong[r]);
}
int quary(int l,int r,ll c)
{
    int ans=0;
    if(blong[l]==blong[r])
    {
        for(int i=l;i<=r;i++)
        {
            if(a[i]+lazy[blong[i]]<c)
            ans++;
        }
        return ans;
    }
    for(int i=l;i<=R[blong[l]];i++)
    {
        if(a[i]+lazy[blong[i]]<c)
        ans++;
    }
    for(int i=blong[l]+1;i<=blong[r]-1;i++)
    {
        //我么要查询的是a[i]+lazy[blong[i]]>=c的第一个位置(vector下标是从零开始的)
        //移项后也就是a[i]>=c-lazy[blong[i]]啦,注意此a[i](指vector容器中的元素)非彼a[i](指原数组)
        ans += lower_bound(v[i].begin(),v[i].end(),c-lazy[i])-v[i].begin();
    }
    for(int i=L[blong[r]];i<=r;i++)
    {
        if(a[i]+lazy[blong[i]]<c)
        ans++;
    }
    return ans;
}
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
inline ll Read(){
    ll  x=0;
    bool f=0;
    char ch=getchar();
    while (ch<'0'||'9'<ch)    f|=ch=='-', ch=getchar();
    while ('0'<=ch && ch<='9')
        x=x*10+ch-'0',ch=getchar();
    return f?-x:x;
}
int main()
{
    n=read();
    for(int i=1;i<=n;i++) a[i]=Read();
    build();
    m=n;
    while(m--)
    {
        int op,l,r;
        ll c;
        op=read(),l=read(),r=read();c=Read();
        if(op==0) update(l,r,c);
        else printf("%d\n",quary(l,r,c*c));
    }
    return 0;
}

问题 C: 数列分块入门 3(待补)

时间限制: 3 S e c 3 Sec 3Sec 内存限制: 256 M B 256 MB 256MB

题目描述

给出一个长为 n n n 的数列,以及 n n n 个操作,操作涉及区间加法,询问区间内小于某个值 x x x 的前驱(比其小的最大元素)。

输入
第一行输入一个数字 n n n
第二行输入 n n n 个数字,第 i i i 个数字为 a i ai ai,以空格隔开。
接下来输入 n 行询问,每行输入四个数字 o p t opt opt l l l r r r c c c,以空格隔开。
o p t = 0 opt=0 opt=0,表示将位于[l,r]的之间的数字都加c。
o p t = 1 opt=1 opt=1,表示询问[l,r]中c的前驱的值(不存在则输出 − 1 -1 1)。

输出
对于每次询问,输出一行一个数字表示答案。

样例输入

4
1 2 2 3
0 1 3 1
1 1 4 4
0 1 2 2
1 1 2 4

样例输出

3
-1

提示
n < = 100000 , − 2 31 < = o t h e r s , a n s < = 2 31 − 1 n<=100000, -2^{31}<=others, ans<=2^{31}-1 n<=100000,231<=others,ans<=2311
待补。。。

问题 D: 数列分块入门 4(树状数组的操作)

时间限制: 1 S e c 1 Sec 1Sec 内存限制: 256 M B 256 MB 256MB
题目描述
给出一个长为 n n n 的数列,以及 n n n 个操作,操作涉及区间加法,区间求和。
输入
第一行输入一个数字 n n n
第二行输入 n n n 个数字,第 i i i 个数字为 a i ai ai,以空格隔开。
接下来输入 n n n 行询问,每行输入四个数字 o p t opt opt l l l r r r c c c,以空格隔开。
o p t = 0 opt=0 opt=0,表示将位于 [ l , r ] [l,r] [l,r]的之间的数字都加 c c c
o p t = 1 opt=1 opt=1,表示询问 [ l , r ] [l,r] [l,r]中的所有数字的和 m o d ( c + 1 ) mod (c+1) mod(c+1)

输出
对于每次询问,输出一行一个数字表示答案。

样例输入

4
1 2 2 3
0 1 3 1
1 1 4 4
0 1 2 2
1 1 2 4

样例输出

1
4

提示
n < = 50000 , − 2 31 < = o t h e r s , a n s < = 2 31 − 1 n<=50000, -2^{31}<=others, ans<=2^{31}-1 n<=50000,231<=others,ans<=2311

what?漏网之鱼呀,竟然没用分块再做一遍,回头得赶紧补上不过这个树状数组也是绝妙啊
来分析一波树状数组:树状数组的区间更改和区间求和
首先:我们知晓对于区间加和区间减,O(1)的操作就是维护一个原数组的差分数组就OK,关键就是区间求和。不难分析出,如果是单点求值的话很容易知道就是差分数组的前缀和,但是区间求和求的是差分数组的前缀和的前缀和,用公式来表示一下就是下方这个,其中b[i]指的是原数组a[i]的差分数组。
S [ x ] = ∑ i = 1 x ∑ j = 1 i b [ j ] S[x]=\sum_{i=1}^{x}\sum_{j=1}^{i}b[j] S[x]=i=1xj=1ib[j]
【数列分块入门】1~9部分题解_第1张图片
上代码:

#include
using namespace std;
typedef long long ll;
const int N = 50010;
ll a[N],n;
//b[i]=a[i]-a[i-1] 
ll tr1[N],tr2[N];//sum1用来维护 b[i]的前缀和,sum2用来维护i*b[i]的前缀和 
ll lowbit(ll x)
{
    return x & -x;
}
void add(ll tr[],ll x,ll c)
{
    for(int i=x;i<=n;i+=lowbit(i))
    {
        tr[i]+=c;
    }
}
ll getsum(ll tr[],ll x)
{
    ll res=0;
    for(int i=x;i;i-=lowbit(i))
    {
        res+=tr[i];
    } 
    return res; 
}
ll getans(ll x)
{
    return (x+1)*getsum(tr1,x)-getsum(tr2,x);
}
int main()
{
    scanf("%lld",&n);
    ll m=n;
    for(int i=1;i<=n;i++)
    {
        scanf("%lld",&a[i]);
        ll c=a[i]-a[i-1];
        add(tr1,i,c);
        add(tr2,i,i*c);
    }
    while(m--)
    {
        ll op,l,r,c;
        scanf("%lld%lld%lld%lld",&op,&l,&r,&c);
        if(op==1)
        {
            printf("%lld\n",(getans(r)-getans(l-1))%(c+1));
        }
        else{
            add(tr1,l,c),add(tr2,l,l*c);
            add(tr1,r+1,-c),add(tr2,r+1,-c*(r+1));
        }
    }
    return 0;
}

问题 E: 数列分块入门 5

时间限制: 1 S e c 1 Sec 1Sec 内存限制: 128 M B 128 MB 128MB

题目描述
给出一个长为 n n n 的数列 a 1 a1 a1, a 2 a2 a2,…, a n an an,以及 n n n 个操作,操作涉及区间开方,区间求和。

输入
第一行输入一个数字 n n n
第二行输入 n n n 个数字,第 i i i 个数字为 a i ai ai,以空格隔开。
接下来输入 n n n 行询问,每行输入四个数字 o p t opt opt l l l r r r c c c,以空格隔开。
o p t = 0 opt=0 opt=0,表示将位于 [ l , r ] [l,r] [l,r]的之间的数字都开平方(下取整)。
o p t = 1 opt=1 opt=1,表示询问 [ l , r ] [l,r] [l,r]中所有数字的和。

输出
对于每次询问,输出一行一个数字表示答案。

样例输入

4
1 2 2 3
0 1 3 1
1 1 4 4
0 1 2 2
1 1 2 4

样例输出

6
2

提示
n < = 100000 , − 2 31 < = o t h e r s , a n s < = 2 31 − 1 n<=100000, -2^{31}<=others, ans<=2^{31}-1 n<=100000,231<=others,ans<=2311

先吐槽一波,1ll*(x)和(ll)x有啥区别吗,不都是强制类型转换嘛,为啥前者就非得害我debug几个小时不止呢,害,都是辛酸泪啊

切入正题,解题思路:
首先看数据范围最大是 2 31 − 1 2^{31}-1 2311,在进行开方操作时,最多也就时开4~5次,就没必要开了,因为开和没开都是一样的,要么这个块中的元素要么为 0 0 0,要么为 1 1 1。所以对于这种开方前和开放后没啥区别的块直接跳过就 O K OK OK,怎么判断是否可以跳过呢,其实这里设一个标记数组就好, l a z y [ x ] = = t r u e lazy[x]==true lazy[x]==true表示这个块在进行开方的操作时可以直接跳过。
这道题多了一个维护区间和的数组 s u m sum sum,初始化的时候 s u m [ x ] sum[x] sum[x]表示 x x x这个块中所有元素的和。每次开方,对于不完整的块是用暴力处理,对于完整的块,如果已经被标记,直接跳过,没被标记同样也是暴力处理(问复杂度的话,别问我,我也不知道,不会超就对了,能力限制,hh )

重头戏:就是怎样进行判断,是否要将这个块给标记呢?其实就是下方的这个代码,变量 t m p tmp tmp等于 0 0 0的话,相当于,开方前和开放后对 s u m sum sum并不产生影响,标记的就是这种块,对,就是这样。

for(int j=L[i];j<=R[i];j++)
{
     tmp+=a[j]-(ll)sqrt(a[j]); 
     a[j]=sqrt(a[j]);
 }
    sum[i] -= tmp;
    if(tmp==0) lazy[i]=true;

上代码:

#include
using namespace std;
typedef long long ll;
const int N=50010;
int n,m,L[N],R[N],block,num,blong[N];
ll a[N],sum[N];//sum用来记录小块的和 
bool lazy[N]; 
void build()
{
    block=sqrt(n);num=n/block; if(n%block) num++;
    R[num]=n;
    for(int i=1;i<=n;i++)
    {
        blong[i]=(i-1)/block+1;
    }
    for(int i=1;i<=num;i++)
    {
        L[i]=(i-1)*block+1,R[i]=i*block;
    }
     
    for(int i=1;i<=num;i++)
    {
        for(int j=L[i];j<=R[i];j++)
        {
            sum[i]+=a[j];
        }
    }
}
void update(int l,int r)
{
    if(blong[l]==blong[r])
    {
        if(lazy[blong[l]]) return;
        ll tmp=0;
        for(int i=l;i<=r;i++)
        {
            tmp+=a[i]-(ll)sqrt(a[i]);
            a[i]=sqrt(a[i]);
        }
        sum[blong[l]]-=tmp;
        return;
    }
    ll tmp=0;
    if(!lazy[blong[l]])
    {
        for(int i=l;i<=R[blong[l]];i++)
        {
            tmp+=a[i]-(ll)sqrt(a[i]);
            a[i]=sqrt(a[i]);
        }
    }
    sum[blong[l]]-=tmp;
    for(int i = blong[l] + 1;i <= blong[r] - 1;i++)
    {
        tmp=0;
        if(lazy[i]) continue;
        else{
            for(int j=L[i];j<=R[i];j++)
            {
                tmp+=a[j]-(ll)sqrt(a[j]);
                a[j]=sqrt(a[j]);
            }
            sum[i] -= tmp;
            if(tmp==0) lazy[i]=true;
        }
    }
    tmp=0;
    if(!lazy[blong[r]])
    {
        for(int i=L[blong[r]];i<=r;i++)
        {
            tmp+=a[i]-(ll)sqrt(a[i]);
            a[i]=sqrt(a[i]);
        }   
    }
    sum[blong[r]]-=tmp;
}
ll quary(int l,int r)
{
    ll ans=0;
    if(blong[l]==blong[r])
    {
        for(int i=l;i<=r;i++)
        {
            ans += a[i];
        }
        return ans;
    }
    for(int i=l;i<=R[blong[l]];i++)
    {
        ans+=a[i];
    }
    for(int i=blong[l]+1;i<=blong[r]-1;i++)
    {
        ans+=sum[i];
    }
    for(int i=L[blong[r]];i<=r;i++)
    {
        ans+=a[i];
    }
    return ans;
}
int main()
{
    scanf("%d",&n);
    memset(lazy,false,sizeof lazy); 
    for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
    memset(sum,0,sizeof sum);
    m=n;
    build();
    while(m--)
    {
        int op,l,r;
        ll c;
        scanf("%d%d%d%lld",&op,&l,&r,&c);
        if(op==0) update(l,r);
        else printf("%lld\n",quary(l,r));
    }
    return 0;
}

问题 F: 数列分块入门 6

时间限制: 1 S e c 1 Sec 1Sec 内存限制: 128 M B 128 MB 128MB

题目描述
出一个长为 n n n 的数列,以及 n n n 个操作,操作涉及单点插入,单点询问,数据随机生成。

输入
第一行输入一个数字 n n n
第二行输入 n n n 个数字,第 i i i 个数字为 a i ai ai,以空格隔开。
接下来输入 n n n 行询问,每行输入四个数字 o p t opt opt l l l r r r c c c,以空格隔开。
o p t = 0 opt=0 opt=0,表示在第 l l l 个数字前插入数字 r r r c c c忽略)。
o p t = 1 opt=1 opt=1,表示询问 a r ar ar 的值( l l l c c c忽略)。

输出
对于每次询问,输出一行一个数字表示答案。

样例输入

4
1 2 2 3
0 1 3 1
1 1 4 4
0 1 2 2
1 1 2 4

样例输出

2
3

提示
n < = 100000 , − 2 31 < = o t h e r s , a n s < = 2 31 − 1 n<=100000, -2^{31}<=others, ans<=2^{31}-1 n<=100000,231<=others,ans<=2311

说一个搞笑的操作,我一开始想测试一下他的数据强不强,直接开动态数组,插入输出,太好笑了,哈哈哈,很幸运地得到了个T,嘿嘿嘿纯属无脑操作,不要学我像这样浪费时间哟。

暴力超时代码:

#include
#include
#include
using namespace std;
int main()
{
	vector<int> v;
	v.reserve(200010);
	int n;
	cin >> n;
	for (int i = 0;i < n;i++)
	{
		int x;
		cin >> x;
		v.push_back(x);
	}
	int m = n;
	while(m--)
	{
		int op, l, r, c;
		cin >> op >> l >> r >> c;
		if (op == 0)
		{
			v.insert(v.begin() + l - 1, r);
			/*for (int i = 0;i < v.size();i++)
			{
				cout << v[i] << " ";
			}
			puts("");*/
		}
		else cout << v[r - 1] << endl;
	}
	return 0;
}

在这里我就是想要放一下我的暴力代码,后来想了想它为啥会超时,这里我给简单的分析一下;首先来看我代码中的这一句

v.reserve(200010);

这一句的作用呢也是为了减少时间而写,了解过 v e c t o r vector vector的内存扩增原理的话,肯定会知道当 v e c t o r vector vector的内存不够用时,他就会以之前二分之三倍的内存进行扩增,扩增完毕之后再将原来的所有数据进行拷贝到新的内存中,并释放原有内存。这个过程是很耗费时间的,尤其是数据多的时候。而 r e s e r v e reserve reserve就很好的起到了在给定内存条件下不进行上述拷贝过程的作用,因为所有数据的内存并未超出初始化是的内存。( p s ps ps 想具体了解vector扩展内存的原理的小伙伴可以来这里学习点击学习 v e c t o r vector vector

显然这个对于在 v e c t o r vector vector尾部插入元素的话就相当于普通数组的操作了,即 O ( 1 ) O(1) O(1)的复杂度,但是新的问题来了,如果是插入在中间的话,那就会很麻烦,比如说在第 k k k个元素前插入一个元素,那么在第 k k k个元素之后(包括第 k k k个元素)的所有元素都要向后移动一位,这个复杂度显而易见是非常的大的,这也就是导致我上面暴力代码超时的主要原因了,至此,暴力代码分析完毕。

又感觉到我的这个暴力想法不是在浪费时间,我学到了呢,hh 好玩儿,嘿嘿

解题思路(分块正解):
同样的,首先将整个区间分成多个小块,然后进行小块处理(感觉这个是废话,hh ).
对于插入操作的分析,我们可以先找到要插入的位置是属于第几块的第几个位置,这就是下方代码中 q u a r y quary quary函数的妙处了。这里有个需要注意的地方,那就是在测试数据中有可能存在在同一位置插入非常多次的操作,那样的话,当下次再处理这个非常多元素的块时是非常耗费时间的,所以就引入了新的操作,当插入的元素达到一定量时再一次对所有元素进行重新分块(至于这里我所说的“一定量”是多少,我也不是很会判断,想要具体了解的话,请直接到文末我所推的博文中去了解)。
对于查询操作,就是利用 q u a r y quary quary函数来找到 r r r是属于第几块第几个元素,最后输出这个元素即可。至此分块思路的分析完毕。

上代码:

#include
using namespace std;
const int N=100010;
typedef pair<int,int> PII;
int st[2*N],a[N],block,num,n,m;
vector<int> v[N];
PII quary(int x)///返回某个位置是在第几个块的第几个位置
{
    int k=1;
    while(x > v[k].size())
    {
        x -= v[k].size(),k++;
    }
    return make_pair(k,x-1);///v中下标从零开始,即每个块的左边界都是0
}
void build()
{
    block=sqrt(n);
    num=n/block;if(n%block) num++;
    for(int i=1;i<=n;i++)
    {
        int idx=(i-1)/block+1;
        v[idx].push_back(a[i]);
    }
}
void rebuild()
{
    int s=0;
    for(int i=1;i<=num;i++)
    {
        for(int j=0;j<v[i].size();j++)
        {
            st[++s]=v[i][j];
        }
        v[i].clear();
    }
    int block2=sqrt(s);
    num=s/block2; if(s%block2) num++;
    for(int i=1;i<=s;i++)
    {
        int idx=(i-1)/block+1;
        v[idx].push_back(a[i]);
    }
}
void insert(int x,int c)
{
    PII tmp=quary(x);
    v[tmp.first].insert(v[tmp.first].begin()+tmp.second,c);
    if(v[tmp.first].size()>20*block) rebuild();
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
    }
    build();
    m=n;
    while(m--)
    {
        int op,l,r,c;
        scanf("%d%d%d%d",&op,&l,&r,&c);
        if(op==0) insert(l,r);
        else{
            PII it=quary(r);
            printf("%d\n",v[it.first][it.second]);
        }
    }
    return 0;
}

问题 G: 数列分块入门 7

时间限制: 1 S e c 1 Sec 1Sec 内存限制: 256 M B 256 MB 256MB

题目描述
给出一个长为 n n n 的数列,以及 n n n 个操作,操作涉及区间乘法,区间加法,单点询问。

输入
第一行输入一个数字 n n n
第二行输入 n n n 个数字,第 i i i 个数字为 a i ai ai,以空格隔开。
接下来输入 n n n 行询问,每行输入四个数字 o p t 、 l 、 r 、 c opt、l、r、c optlrc,以空格隔开。
o p t = 0 opt=0 opt=0,表示将位于 [ l , r ] [l,r] [l,r]的之间的数字都加 c c c
o p t = 1 opt=1 opt=1,表示将位于 [ l , r ] [l,r] [l,r]之间的数字都乘 c c c
o p t = 2 opt=2 opt=2,表示询问 a r ar ar的值 m o d 10007 mod 10007 mod10007( l l l c c c忽略)

输出
对于每次询问,输出一行一个数字表示答案。

样例输入

7
1 2 2 3 9 3 2
0 1 3 1
2 1 3 1
1 1 4 4
0 1 7 2
1 2 6 4
1 1 6 5
2 2 6 4

样例输出

3
100

提示
n < = 100000 , − 2 31 < = o t h e r s , a n s < = 2 31 − 1 n<=100000, -2^{31}<=others, ans<=2^{31}-1 n<=100000,231<=others,ans<=2311

解题思路:
区间乘法和区间加法,欧呦,双管齐下呐,厉害。单独来看的话非常好操作就和数列分块1是类似的,不难操作。
这道题呢是两个操作的结合,一个懒标的话估计是不够,那么我们就来维护两个懒标,一个是维护乘法的懒标,一个是维护加法的懒标,接下来我们要找到这两个标记之间的联系;分析如下:
我们假设数组中某一元素的初始值为 x x x,经过入干次乘和加的操作后所得到的元素值为 N N N;
N = m u l ∗ x + a d d N=mul*x+add N=mulx+add
对于加的操作,就是 a d d add add加上相应的值就好;而对于乘的话,比如说乘的操作多了1,相应的 a d d add add也同样多了一倍,也就是说在 m u l mul mul增加了 k k k那么 a d d add add就要变为 k ∗ a d d k*add kadd.
我解释的不太清楚,如果不懂的话也可以参考懒标之间的联系自我感觉这位大佬分析得非常清楚。

注意:在完整块中,对于整块的加法标记进行更新时,单独更新就好;对于乘法标记的更新,同时也要将加法标记连带上一起更新。在不完整的快中,需要暴力的将这个块中的所有元素的标记都给附加在数组元素中,也就是说给加的加,该乘的乘,把标记都给用完,之后在进行暴力更新不完整快中的元素。这里引入 p u s h d o w n pushdown pushdown函数,具体见代码。对于区域操作,那就很常见了,这里不过多解释,有关乘法的取余可参考我的另一篇博客取模运算(代码中有关于乘法取模的运算解释,欢迎来访)

上代码:

#include
using namespace std;
const int N=100010;
const int mod=10007;
int n,m,block,num,L[N],R[N],blong[N],a[N];
int add[N],mul[N];///维护加法标记和乘法标记
void build()
{
    block=sqrt(n);
    num=n/block; if(n%block) num++;
    for(int i=1;i<=n;i++)
    {
        blong[i]=(i-1)/block+1;
    }
    for(int i=1;i<=num;i++)
    {
        L[i]=(i-1)*block+1,R[i]=i*block;
    }
    R[num]=n;
    for(int i=1;i<=num;i++)
    {
        add[i]=0,mul[i]=1;
    }
}
void pushdown(int x)
{
    for(int i=L[x];i<=R[x];i++)
    {
        a[i]=((a[i]%mod*mul[x]%mod)%mod+add[x])%mod;
    }
    add[x]=0,mul[x]=1;
}
void update1(int l,int r,int c)
{
    if(blong[l]==blong[r])
    {
        pushdown(blong[l]);
        for(int i=l;i<=r;i++)
        {
            a[i] = (a[i] + c) % mod;
        }
        return;
    }
    pushdown(blong[l]);
    for(int i=l;i<=R[blong[l]];i++)
    {
        a[i] = (a[i] + c) % mod;
    }
    for(int i=blong[l]+1;i<=blong[r]-1;i++)
    {
        add[i] += c;
    }
    pushdown(blong[r]);
    for(int i=L[blong[r]];i<=r;i++)
    {
        a[i] = (a[i] + c) % mod;
    }
}
void update2(int l,int r,int c)
{
    if(blong[l]==blong[r])
    {
        pushdown(blong[l]);
        for(int i=l;i<=r;i++)
        {
            a[i]=(a[i]%mod*c%mod)%mod;
        }
        return;
    }
    pushdown(blong[l]);
    for(int i=l;i<=R[blong[l]];i++)
    {
        a[i]=(a[i]%mod*c%mod)%mod;
    }
    for(int i=blong[l]+1;i<=blong[r]-1;i++)
    {
        mul[i] = (mul[i]%mod * c%mod) % mod;
        add[i] = (add[i]%mod * c%mod) % mod;
    }
    pushdown(blong[r]);
    for(int i=L[blong[r]];i<=r;i++)
    {
        a[i]=(a[i]%mod*c%mod)%mod;
    }
}
int quary(int x)
{
    return ((a[x]%mod*mul[blong[x]]%mod)%mod+add[blong[x]])%mod;
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
    }
    build();
    m=n;
    while(m--)
    {
        int op,l,r,c;
        scanf("%d%d%d%d",&op,&l,&r,&c);
        if(op==0)
        {
            update1(l,r,c);
        }
        else if(op==1)
        {
            update2(l,r,c);
        }
        else
        {
            printf("%d\n",quary(r));
        }
    }
    return 0;
}

问题 H: 数列分块入门 8(待补)

时间限制: 1 S e c 1 Sec 1Sec 内存限制: 128 M B 128 MB 128MB

题目描述
给出一个长为 n n n 的数列,以及 n n n 个操作,操作涉及区间询问等于一个数 c c c 的元素,并将这个区间的所有元素改为 c c c

输入
第一行输入一个数字 n n n
第二行输入 n n n 个数字,第 i i i 个数字为 a i ai ai,以空格隔开。
接下来输入 n n n 行询问,每行输入三个数字 l 、 r 、 c l、r、c lrc,以空格隔开。
表示先查询 [ l , r ] [l,r] [l,r]的数字有多少个是 c c c,再把位于 [ l , r ] [l,r] [l,r]的数字都改为 c c c

输出
对于每次询问,输出一行一个数字表示答案。

样例输入

4
1 2 2 4
1 3 1
1 4 4
1 2 2
1 4 2

样例输出

1
1
0
2

提示
n < = 100000 , − 2 31 < = o t h e r s , a n s < = 2 31 − 1 n<=100000, -2^{31}<=others, ans<=2^{31}-1 n<=100000,231<=others,ans<=2311

蒟蒻暂时还没做出来 ,做出来后再回来更。。。

问题 I: 数列分块入门 9(待补)

时间限制: 3 S e c 3 Sec 3Sec 内存限制: 256 M B 256 MB 256MB

题目描述
给出一个长为 n n n的数列,以及 n n n 个操作,操作涉及询问区间的最小众数。

输入
第一行输入一个数字 n n n
第二行输入 n n n 个数字,第 i i i 个数字为 a i ai ai,以空格隔开。
接下来输入 n n n 行询问,每行输入两个数字 l 、 r , l、r, lr以空格隔开。
表示查询位于 [ l , r ] [l,r] [l,r]的数字的众数。

输出
对于每次询问,输出一行一个数字表示答案。

样例输入

4
1 2 2 4
1 2
1 4
2 4
3 4

样例输出

1
2
2
2

提示
n < = 100000 , − 2 31 < = o t h e r s , a n s < = 2 31 − 1 n<=100000, -2^{31}<=others, ans<=2^{31}-1 n<=100000,231<=others,ans<=2311

蒟蒻暂时还没做出来 ,做出来后再回来更。。。
附大佬链接巨巨

现更于2020年6月1日,儿童节快乐,撒花撒花撒花,✌
在这里插入图片描述在这里插入图片描述

你可能感兴趣的:(【数列分块入门】1~9部分题解)