【数据结构】分块(2/8)希望能抓到8月份的小尾巴

时间:2021-08-23
本文章向大家介绍【数据结构】分块(2/8)希望能抓到8月份的小尾巴,主要包括【数据结构】分块(2/8)希望能抓到8月份的小尾巴使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

分块

分块的意思

分块就是将一段长区间(寿司条)分成一段段规格尽量统一的寿司,但技艺不精的新人厨师有的时候会不小心再两端的一端切出不符合规格标准(没有提前算好平均下去的规格,只是按既定的规格)的区间(从寿司的角度来理解,可以看成是边角料,从区间的角度,就是需要进行特殊处理的区间)。

把一个长度为\(n\)的区间分成几块看操作者的个人意愿,但这里建议分成\(\sqrt{n}\)块,为啥呢?

假设将长度为n的区间分成m块,每块长度就会为\([\frac{n}{m}]\),一般的话块与块之间的大小是相等的,同时也会把整个块当成一整份一起处理,但也有时会剩下一块或两块长度不一样(比较尴尬)的区间(看成是边角料吧,暂且称为零散块),而这块比较尴尬的块的区间长度是小于\([\frac{n}{m}]\) ,因而一次想要对长度为n的区间整个区间处理掉的话,就需要\(O(m+\frac{n}{m})\)(m是整块进行处理的块数,\(\frac{n}{m}\)是对零散块内所有的元素的个数,这里在最终形成\(m+\frac{n}{m}\)的时候应该是做了一些近似),根据均值不等式可知在\(m=\sqrt{n}\)的时候时间复杂度大O可以取到最小值,即\(O(\sqrt{n})\)(也称这种算法叫做根号算法),这种取法也意味着将长度为\(n\)的区间最终分成了长度为\(\sqrt{n}\)\(\sqrt{n}\)块的区间,

如何写分块

首先,我们要定下标准,确定每一块块的规格,设规格变量为t。

inr t = sqrt(n);

然后每一块本质是一个区间,一个区间的的话,他就有区间本身的一些特性,比如说区间的左端点和右端点,用st数组来记录每一个区间的左端点,和用ed数组用来记录每一个区间的右端点,比如st[i],ed[i]表示的是第i个区间的左端点和右端点。

for(int i = 1;i <= t;i++)
{
    st[i] = (i-1)*t+1;
    ed[i] = i*t;
}

处理最后一块的两种写法

蓝书(开辟新的块)

t++;st[t]=ed[t-1]+1;ed[t]=n;

Pecco(拉长最后一块至包含到结尾)

注意,我们在分的过程中最后一块可能不是符合规格的块,我们要将最后一块所代表的区间的假设的右端点重新修改为真实的端点值。(拉长到实际长度)(为什么是拉长?是因为int t = sqrt(n);t相当于是根号n截尾后的数字,截尾截尾,截取一部分,相当于数字减少,所以\(t\times t<=n\)

ed[t] = n;

反过来,我们还需要对区间内的每一个元素打上标记,表明这个元素是属于哪一个块,取belong(归属)的前三个字母bel作为数据的名字,同时之前我们已经记好了每一个的块的左端点和右端点的位置,所以当我们要找到一个块里面的所有元素的位置的时候,我们只需要从左端点扫到右端点就行。

for(int i = 1;i <= t;i++)
    for(int j = st[i]; j <= ed[i]; j++)
        bel[j] = i;
  • 代码注释,j代表的是第i块里面的元素,即元素j是属于第i块的。

同时,通过左端点位置和右端点位置的差值的绝对值,我们可以也可以得到每一个块的区间长度:(比较鸡肋)(注意,如果使用万能库的话,会发生冲突)

for(int i = 1; i <= t;i++)
    size[i] = ed[i] - st[i] + 1;

除此之外,我们还可以用一个sum数组用来统计区间和

for(int i = 1; i <= n; i++)
    cin>>A[i];
for(int i = 1; i <= t; i++)
    for(int j = st[i]; j <= ed[i]; j++)
        sum[i] += A[j];

分块的用途

实现对区间l到r的加减,并最终能够查询到区间内某一个点的值。

区间修改(加减部分)

  • 增量标记add,有点类似线段树的懒标记,如果对整个块进行加减的话,就可以使用增量标记来说明整个块加了多少(最终再进行结算),那如果是处理到零散块,就暴力解决或者sum[i] = (et[i]-l+1) * d( sum[i] = (r-st[i]+1)*d )

l和r在同一块

if( bel[l] == bel[r])
{
     for(int i = l; i <= r ; i++)
     {
           A[i]++;
           sum[ bel[i] ]+=k;
     }
}

l和r不在同一块

  • 零散块暴力处理,整块整块处理
if( bel[l] != bel[r] )
{
     for(int i = l; i <= ed[ bel[l] ] ; i++)
     {
         A[i]++;
         sum[ bel[i] ] += k;
     }
     
     for(int i = bel[l] + 1 ; i <= bel[r] - 1 ; i++)
         add[i] += k;
         
     for(int i = st[ bel[r] ] - 1 ; i <= r ; i++)
     {
         A[i]++;
         sum[ bel[i] ] += k;
     }
}
LOJ#6277. 数列分块入门 1

#include <bits/stdc++.h>
#define ll long long
#define rep(i,x,n) for(int i=x;i<n;i++)
#define repd(i,x,n) for(int i=x;i<=n;i++)
using namespace std;
const int N = 5e4+10;
int st[N],ed[N],add[N],A[N],bel[N];
int n,m,t;
void modi(int l,int r,int k) {
	if(bel[l]==bel[r])
		repd(i,l,r)
		    A[i]+=k;

	else {
		repd(i,l,ed[ bel[l] ])
		A[i]+=k;

		repd(i,bel[l]+1,bel[r]-1)
		add[i]+=k;

		repd(i,st[ bel[r] ],r)
		A[i]+=k;
	}
}
int main() {
	memset(add,0,sizeof(add));
	
	scanf("%d",&n);
	t=sqrt(n);

	repd(i,1,t) {
		st[i]=(i-1)*t+1;
		ed[i]=i*t;
	}

	if(ed[t]<n) t++,st[t]=ed[t-1]+1,ed[t]=n;

	repd(i,1,t)
     	repd(j,st[i],ed[i])
	        bel[j]=i;

	repd(i,1,n)
	scanf("%d",&A[i]);

	int opt,l,r,c;
	int k=n;
	while(k--) {
		scanf("%d%d%d%d",&opt,&l,&r,&c);
		if(opt==1) printf("%d\n",add[ bel[r] ]+A[r]);
		else
			modi(l,r,c);
	}
	return 0;
}

实现对区间l到r的加减,并最终能够读取任意的区间和

#include <bits/stdc++.h>
#define MEM(a,x) memset(a,x,sizeof(a))
#define W(a) while(a)
#define gcd(a,b) __gcd(a,b)
#define pi acos(-1.0)
#define PII pair<int,int>
#define pb push_back
#define mp make_pair
#define fi first
#define se second
#define ll long long
#define ull unsigned long long
#define rep(i,x,n) for(int i=x;i<n;i++)
#define repd(i,x,n) for(int i=x;i<=n;i++)
#define MAX 1000005
#define MOD 1000000007
#define INF 0x3f3f3f3f
#define lowbit(x) (x&-x)
#define de() cout<<"res = "<<res<<endl;
using namespace std;
const int NN = 320, N = 1E5+10;

int st[NN],ed[NN],add[NN],t,A[N],bel[N];
ll sum[N];

ll ask(int l,int r)
{
	ll res=0;
	if(bel[l]==bel[r])
	{
	    res = add[ bel[l] ] * (r-l+1);
		repd(i,l,r)
		    res+= A[i];
		return res;
	}
	
	//整块的处理 
	repd(i,bel[l]+1,bel[r]-1)
		res += add[i] * (ed[i] - st[i] + 1) + sum[i];

	//零散块
	repd(i,l,ed[ bel[l] ])
	    res += A[i] + add[ bel[i] ]; 
	    
	repd(i,st[ bel[r] ],r)
		res += A[i] + add[ bel[i] ];

    return res;
}

void modi(int l,int r,int k)
{
	if(bel[l]==bel[r])
	{
		repd(i,l,r) A[i] += k;
		sum[ bel[l] ] += (r-l+1) * k;
	}
	else
	{
		repd(i,l,ed[ bel[l] ])
		{
			A[i] += k;
			sum[ bel[i] ] += k;
		}
		
		repd(i,bel[l]+1,bel[r]-1)
			add[i] += k;
		    	    
		repd(i,st[ bel[r] ],r)
		{
			A[i] += k;
			sum[ bel[i] ] += k;
		}
	}
}

int main()
{
    memset(sum,0,sizeof(sum));
    memset(add,0,sizeof(add));
    int n,q;
    cin>>n>>q;
    
    int t = sqrt(n);

    repd(i,1,t)
    {
        st[i] = (i-1)*t+1;
        ed[i] = i*t;	 
	}
	if(ed[t]<n) t++,st[t] = ed[t-1] + 1,ed[t] = n; 
	
	repd(i,1,t)
	    repd(j,st[i],ed[i])
	    	 bel[j] = i;
	
	repd(i,1,n)
	{
	    cin>>A[i];
		sum[ bel[i] ] += A[i];	
	}
    
    int l,r,k;
	char op;
    while(q--)
    {
    	cin>>op;
        cin>>l>>r;
        if(op=='Q') cout<<ask(l,r)<<endl;
        else
        {
        	cin>>k;
        	modi(l,r,k);
	}
    }
    return 0;
}

实现对区间l到r的加减,并最终能够查询到区间内大于某个特定的值的个数

先考虑一个简化版的问题,如果给你一个比较小的区间,让你直接查询这个区间内大于等于c的数字的个数,你该怎么做?

  • 朴素的想法就是直接扫过去,满足条件的就记录的计数器里面去
  • 第二种想法是先对这个区间排个序,找到第一个大于这个特定值的数字的位置,那么在这个位置后面的数字都将会满足条件(答案也就是位置差)。

资源

好吃的寿司做法!手把手教你做,学会再也不用出去买了

原文地址:https://www.cnblogs.com/BeautifulWater/p/15178011.html