讲讲分块算法 题目Lucas的数列

Description

在这里插入图片描述

Input

在这里插入图片描述

Output

在这里插入图片描述

Sample Input

5 5
1 2
2 3
3 4
4 5
5 6
1 2 4
1 3 0
1 5 3
1 5 2
5 5 0

Sample Output

1
empty
6
1
empty

Data Constraint

在这里插入图片描述

solution

先说一下题意,给定一个a数组,每次询问l到r之间复杂度不大于z的方差。

20分做法

直接模拟?嗯没错,但是一不小心还是会wa
为什么呢,由于题目的数据过于**,注意到n方乘t方小于等于1e18,这说明当n十分小的时候(n<=5)t会极大,导致不会爆longlong,但是也会爆double的精度。
那么如何做呢,我们设S表示1到m之间x的总和,F表示1到m之间x^2的总和,然后把方差那一项拆开:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
由于我们只是担心精度,所以我们只需把除法给省掉就行了。
我们又知道:

在这里插入图片描述
所以上述式子中的m与p可以抵消,得:
在这里插入图片描述
所以就是:
在这里插入图片描述
最后可得
在这里插入图片描述
OK,那么答案就上面啦,这样子直接模拟就有二十分啦。
那么我们的问题就变成了,如何快速求得x总和与x方总和,也就是快速知道x是哪些。
那么关于这个,说一下惨痛的做题史:
昨天,也就是8.05早上,一如既往,开始了比赛,T1dp求逆序对优化搞了好久,放弃,拿到60分后开始莽T2,也就是这题。打了二十分暴力后,10:00开始想选择打T2正解还是T3部分分(这时T2有了分块思路),然后还是选择先莽T360分,之后发现还有1个小时。然后直接莽T2的分块。但是分块这个东西是前几天才接触到的,也从来没打过,但是感觉这次很有感觉,选择了搏一搏。(这件事让我想起几周前有一道关于树形结构的题,正解是最小生成树+一些操作,但是我选择莽树剖,(这时树剖就对着板子码过一次)调过好久终于a了,然后树剖也直接彻底掌握了,这次同理,这道题让我真正理解分块,顺便发下那道树形结构题)但是很不熟练,所以没打完就到时间了,然后原来二十分由于覆盖而且c++出了点小问题所以找不到了,最后是120分第六名。
但是即是这样,由于分块搞了许久,所以不想放弃,也在一直尝试,也看了下题解,是离线加线段树(树状数组)(还有操作与区间排序之类的)维护的,并没有谈分块做法,心里确实有点虚,但是还是一直尝试,拖了40分钟才去恰饭。这时也没改出来。
然后下午讲题前也在搞,讲正解时也没听,相当于是背水一战了。然后改题时将T1T3A了以后又开始了。
这时过了样例,然后交上去WA了,发现没有TLE(2s,跑了800ms)之后一直搞搞了好久才发现边边两块只能暴力,不能同中间一样二分(这个地方实际是分块的基本操作,分块就是中间快速求,两边暴力求得,但是当时没想那么多)之后改改交了上去,期间心想不要TLE,不要TLE,结果一出,1600ms,AC!开心。
所以通过这次,也是真的理解分块了。

分块的优势

首先如同上题,大家线段树(树状数组更快)都是300ms,而分块却很慢,因为线段树是nlogn的,但是分块却是nsqrt(n)的。这么一看感觉分块能干的事情线段树都能干啊,而且更好?是的,但是关于这个分块,凭我自己的感受,是真的香。首先它打起来确实挺长的,其次跑的不算快(一般就是n<=50000吧,这题虽然说n很大,但是竟然跑进了,也让我很吃惊)但是呢,它有一个最显著的地方,就是无脑,这个玩意儿比线段树还无脑,如这道题我一开始感觉线段树不可做,然后就想到了分块了,并且这个东西基本上就是套板子(线段树也一样,但是线性结构总比树形结构稳一点,起码出的锅不算多)真的没什么技术含量,所以其有美名曰优雅的暴力,实质上的确。其次,有些题涉及的数据结构比较偏,如前几天遇到个题,正解就是分块,但是有种叫做吉司机线段树(多用来求区间max)也可以做,像这种时候常见的数据结构维护不了时,就可以用分块,并且十分方便。

然后说一下,可能你感觉分块十分复杂,但是实际上呢,它就是个套模板的东西,我原来也觉得这东西没必要,但是去接触一下后,看看模板后感觉十分简单上手。当在考试中,若不会正解,一般跑个分块分数也可以拿个60分甚至更高(如这道题80分可以打主席树,但是显然脱出noip考纲而且显然更难打)

好了现在以这道题为例讲一下(等会代码可能很丑,但是会标注的很详细)
首先我们知道这道题就是在l到r中找到时间复杂度不大于z的数对吧,前面也提到过,分块就是说旁边暴力,中间快速求(分块分块,顾名思义就是把数列分成几块几块,然后来求),那么中间如何快速求呢?
我们先提出一个很简单的问题,给个序列,每次询问序列中<=z的数的和,序列长度<=1000000,询问次数<=500000
那么有一个很显然的思路,将序列排序,记录前缀和,然后每次询问直接二分就好了。
ok,如同上面的思想,这道题而言,首先,对于一个询问,显然的,是由几个块组成的,然后左边右边的块不一定完整,但是中间的每一块都是完整的。所以如同上面,对于中间的每一块,我们可以直接二分。那么左右怎么求呢?这就涉及到我们如何分块了,一般来讲,我们的每一块都是取sqrt(n)最优,这样时间复杂度就保证在nsqrt(n)了,当然根据题目,不同的分块方式可能使时间复杂度更优。那么左右两块直接暴力枚举求就可以了(当然你想的话可以套个线段树维护……emm好吧当我没说
然后上面的方法算下复杂度,m次询问,每次询问l到r,最多找sqrt(n)次块,两边暴力O(sqrt(n))求,中间O(log(sqrt(n)))来求,所以最坏时间复杂度是O(m sqrt(n)log(sqrt(n)))
对于这题,没有修改只有询问,当然分块也可以处理修改的,处理方法类似,只要记住两边一定暴力,中间快速算即可。
然后再扯一嘴,若是l与r同属一个块的话,就直接暴力就行了。
好了该讲的也讲了,还有什么不懂的代码里见。

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
#define N 500007
using namespace std;
int n,q,num,w,l[N],r[N],home[N];
//n为数组长度,q为询问,num为分块的块数,w为块的长度,l,r是每个块的左右端点,home是每个点属于的块
long long fang<
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值