【OI】简单的分块

介绍下简单的分块:

 

当我们遇到区间类问题的时候,如何保证我们快速而高效地完成操作?

答案是线段树分块。

所谓分块,就是把一个序列分成许多块分别维护。是不是想起了树状数组 这样能大大提高效率:

例如,我们需要查询l,r中所有元素的和

利用分块,我们可以把1 2 3 4 5 6 7 8 9 10

分为

[1 2 3] [4 5 6] [7 8 9] [10]

比如,我想要3~10的元素和。这是,我们拿出3~10的区间:

...[3] [4 5 6] [7 8 9] [10] 

而我们已经预处理了 [4 5 6]与 [7 8 9]的区间和,我们只需要算出两端的和就可以。这样,就可以大大提高查询的速度。

#include <cstdio>
#include <cmath>
#include <algorithm>

using namespace std;

const int MaxN = 100010, MaxB = 1010;

int n, B, q, Cnt;
int a[MaxN], Left[MaxB], Right[MaxB], Pos[MaxN];
long long sum[MaxN];

int main()
{
	scanf("%d", &n);
	for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
	// 分块预处理 
	B = max((int)sqrt(n), 1);
	for (int i = 1; i <= n; i++)
	{
		if (i % B == 1 % B) Cnt++; // 每块块首 Cnt++ 
		Pos[i] = Cnt; // i 处在哪一块 
		if (Left[Cnt] == 0) Left[Cnt] = i; // 第Cnt块的左边 
		Right[Cnt] = i; // 第Cnt块的右边
	}

// for (int i = 1; i <= n; i++) printf("%d ", Pos[i]); puts("");
// for (int i = 1; i <= Cnt; i++) printf("%d %d\n", Left[i], Right[i]);

	for (int i = 1; i <= Cnt; i++)
		for (int j = Left[i]; j <= Right[i]; j++)
			sum[i] += a[j];	// sum[i] 表示的是第i块的和 

	// 处理询问 
	scanf("%d", &q);
	//	a[l] + a[l + 1] + ... + a[r] 变成 sum[r] - sum[l - 1]
	//	这样我们可以只用求 l=1 的情况 
	//	但是我现在不这样做 
	for (int i = 1; i <= q; i++)
	{
		int l, r;
		scanf("%d %d", &l, &r);
		long long ans = 0;
		if (Pos[l] == Pos[r])
		{
			// 1   l,r 在同一个块内
			// [  l     r  ]
			for (int j = l; j <= r; j++) ans += a[j];
		}
		else
		{
			// 2   l,r 不在同一个块内
			// [  l   ] [      ] [       ] [r      ]
			for (int j = l; j <= Right[Pos[l]]; j++) ans += a[j];
			for (int j = Right[Pos[l]] + 1; j <= Left[Pos[r]] - 1; j++) ans += sum[j];
			for (int j = Left[Pos[r]]; j <= r; j++) ans += a[j];
		}
		printf("%lld\n", ans);
	}
	return 0;
}

(不是我写的,但是的确十分简洁易懂对吧)

 

例题:luogu P3203,LCT板子题可是可以用分块来过

#include<iostream>
#include<vector>
#include<cstdio>
#include<queue>
#include<map>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<set>
#include<cstring>
using namespace std;
typedef long long ll;
const ll INF=99999999;
const int MaxN = 200010;
int a[MaxN];
struct nd{
    int t,to;//弹几次出此块 
    int from;//属于第几个块 
    
}t[MaxN];
int n,m,I,J; 
int sqr,cnt = -1;
int Left[MaxN],Right[MaxN];

void findT(int x,int right,int &t,int &to){
    int re = 0;
    int i = x;
    for(;i <= right;){
        int ad = a[i];
        i += ad;
        re++;
        
    }
    t = re;
    to = i;
//printf("需要%d步跳出,跳到%d\n",t,to); 
    
} 

inline int Max(int a,int b){
    if(a>b) return a;
    return b;
    
}

int main()
{
    //freopen("testdata.in","r",stdin);
    //freopen("testdata.out","w",stdout);
    scanf("%d",&n);
    for(int i = 0;i < n; i++){
        scanf("%d",&a[i]);
        
    }    
    
    sqr = Max(sqrt(n),1);
    
    /*for(int i = 0;i < n; i++){
        if(i%sqr == 0%sqr) {
            cnt++;
            Left[cnt-1] = i;
//printf("第%d个块左边是%d\n",cnt-1,i);
        }
        Right[cnt-1] = i;
        t[i].from = cnt-1;
//printf("右边界:%d\n",sqr*cnt-1);        
        findT(i,sqr*cnt-1,t[i].t,t[i].to);
        
    }*/
    
    for(int i = 0;i < n; i++){
        if(i%sqr == 0%sqr){
            Left[++cnt] = i;
            
        }
        t[i].from = cnt;
        Right[cnt] = i;
    }
    
    for(int i = n-1;i >= 0;i--){
        t[i].to = i+a[i];
        if(t[i].to >= Right[t[i].from]) t[i].t = 1;
        else t[i].t=t[t[i].to].t+1,t[i].to=t[t[i].to].to;
//printf("%d\n",t[i].to);
    }
/*
for(int i = 0;i < n; i++){
    printf("第%d个元素:跳%d次跳出此块(%d~%d)到%d,属于第%d个块\n",i,t[i].t,Left[t[i].from],Right[t[i].from],t[i].to,t[i].from); 
}*/
    scanf("%d",&m);
    for(int i = 0;i < m; i++){
        scanf("%d%d",&I,&J);
        int k;
        if(I == 2){
            
            
            scanf("%d",&k);
            a[J] = k;
            for(int j = Right[t[J].from];j >= Left[t[J].from]; j--){
                t[j].to = j+a[j];
                if(t[j].to >= Right[t[J].from]) t[j].t = 1;
                else t[j].t=t[t[j].to].t+1,t[j].to=t[t[j].to].to;
                
            }
        }
        else{
            int ans = 0;
            while(J < n){
                ans += t[J].t;
                J = t[J].to;
                
            }
            printf("%d\n",ans);
            
        }
        
        
    }
    
    
    return 0;
}
P3203

利用分块思想,每一个元素维护跳几次跳出这个块以及跳到哪里。

这样,就可以大大提高查询的速度,但是问题在于如何O(n)的预处理。

预处理:从后往前枚举,就好像穿起一条链。比如:

设t[i]为需要跳t[i]次跳出某个块,to[i]为跳出某个块到to[i]。

假设这个序列为:

... 1 1

第n个元素,处于第x块,则需要1次跳出块到n+a[i]。(t[i] = 1,to[i] = 4)

第n-1个元素,处于第x块(假设处于同一个块),则需要2次跳出块到4。(t[i] = t[to[i]]+1 = 2,to[i] = to[to[i]],相信不难理解)

以此类推。

这样,只要在修改的时候对于修改元素所在的块进行我们写好的预处理。

转载于:https://www.cnblogs.com/dudujerry/p/10337181.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
基于微信小程序的家政服务预约系统采用PHP语言和微信小程序技术,数据库采用Mysql,运行软件为微信开发者工具。本系统实现了管理员和客户、员工三个角色的功能。管理员的功能为客户管理、员工管理、家政服务管理、服务预约管理、员工风采管理、客户需求管理、接单管理等。客户的功能为查看家政服务进行预约和发布自己的需求以及管理预约信息和接单信息等。员工可以查看预约信息和进行接单。本系统实现了网上预约家政服务的流程化管理,可以帮助工作人员的管理工作和帮助客户查询家政服务的相关信息,改变了客户找家政服务的方式,提高了预约家政服务的效率。 本系统是针对网上预约家政服务开发的工作管理系统,包括到所有的工作内容。可以使网上预约家政服务的工作合理化和流程化。本系统包括手机端设计和电脑端设计,有界面和数据库。本系统的使用角色分为管理员和客户、员工三个身份。管理员可以管理系统里的所有信息。员工可以发布服务信息和查询客户的需求进行接单。客户可以发布需求和预约家政服务以及管理预约信息、接单信息。 本功能可以实现家政服务信息的查询和删除,管理员添加家政服务信息功能填写正确的信息就可以实现家政服务信息的添加,点击家政服务信息管理功能可以看到基于微信小程序的家政服务预约系统里所有家政服务的信息,在添加家政服务信息的界面里需要填写标题信息,当信息填写不正确就会造成家政服务信息添加失败。员工风采信息可以使客户更好的了解员工。员工风采信息管理的流程为,管理员点击员工风采信息管理功能,查看员工风采信息,点击员工风采信息添加功能,输入员工风采信息然后点击提交按钮就可以完成员工风采信息的添加。客户需求信息关系着客户的家政服务预约,管理员可以查询和修改客户需求信息,还可以查看客户需求的添加时间。接单信息属于本系统里的核心数据,管理员可以对接单的信息进行查询。本功能设计的目的可以使家政服务进行及时的安排。管理员可以查询员工信息,可以进行修改删除。 客户可以查看自己的预约和修改自己的资料并发布需求以及管理接单信息等。 在首页里可以看到管理员添加和管理的信息,客户可以在首页里进行家政服务的预约和公司介绍信息的了解。 员工可以查询客户需求进行接单以及管理家政服务信息和留言信息、收藏信息等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值