莫队--------一个优雅的暴力

莫队是一个可以在O(n√n)内求出绝大部分无修改的离线的区间问题的答案(只要问题满足转移是O(1)的)即你已知区间[l,r]的解,能在O(1)的时间内求出[l-1,r][l+1,r][l,r-1][l,r+1]的解。否则时间复杂度为O(kn√n)(k为转移的时间)

以下默认转移是O(1)的

显然,我们如果得知[l,r]的解,我们便可以在O(|l2-l|+|r2-r|)的时间内求出[l2,r2]的解

那么,对于q个询问(假设q与n同数量级),我们如果能找到一个合适的顺序求解这q个询问,便能降低时间复杂度

对于最原始的暴力,我们每次从l遍历到r来求解(l,r为区间查询的左右端点),复杂度O(n^2)

试着找到一个合适的求解顺序,我们把l,r想象成一个平面直角坐标系上的点(l,r),则所有询问间转移的花费就为在沿着这个平面上最小直线斯坦纳树做的花费。

挺简单的是吧?

但是我们有个良好的替代品,分块。

我们把整个数列分成T块(T=√n),然后我们记下每个询问的左端点l所在的块block[i],按block为第一关键字,r为第二关键字从小到大排序,依次求解就能使时间复杂度降至O(n√n)

简单说明一下:

左指针移动最坏情况下是从一块的开始跳到结尾然后再跳回开始......(因为你从这块跳到下一块后就无法跳回来了)时间复杂度O(n√n)

右指针移动最坏情况下是对于每个在不同块内的左指针,右指针都要从头跳到尾(在同一块内的多个左指针,从头跳到尾的过程中肯定也一起处理了)时间复杂度O(n√n)

在有些情况下,如果莫队的删除操作时间复杂度过高,而添加操作的时间复杂度极低,那我们考虑维护询问左端点所在的块的右端点到询问右端点之间的答案(左右端点所在块相同的暴力for一遍处理) (左端点到左端点所在的块的右端点的答案暴力计算)具体参见[BZOJ4241]历史研究(回滚莫队)

此外,莫队还有一个很好的Debug的方法,就用样例,不断调整分块大小T,看看答案是否不变。

[洛谷2709] 小B的询问

题目描述

小B有一个序列,包含N个1~K之间的整数。他一共有M个询问,每个询问给定一个区间[L..R],求Sigma(c(i)^2)的值,其中i的值从1到K,其中c(i)表示数字i在[L..R]中的重复次数。小B请你帮助他回答询问。

输入输出格式

输入格式:

第一行,三个整数N、M、K。

第二行,N个整数,表示小B的序列。

接下来的M行,每行两个整数L、R。

输出格式:

M行,每行一个整数,其中第i行的整数表示第i个询问的答案。

输入输出样例

输入样例

6 4 3
1 3 2 1 1 3
1 4
2 6
3 5
5 6
输出样例

6
9
5
2

说明

对于全部的数据,1<=N、M、K<=50000

我们这道题为例讲讲莫队的实现,首先排序,注意排序是把询问排序,不是叫你把原数组排序

然后初始化双指针l=1,r=0,表示我们已经得知[l,r]的答案

我们用cnt数组记录每个数的出现次数(如果K<=10^9,那么需要离散化)

然后莫队最难的地方来了,转移。即用区间[l,r]求出区间[l-1,r][l+1,r][l,r-1][l,r+1]的值

由完全平方公式可知,区间中多出一个数字i,对答案的贡献为i之前的出现次数(即cnt[i])*2+1,区间中少一个数字i,对答案的贡献为-cnt[i]*2+1

语序!!!显然,我们是先计算答案后cnt++(--),但是l和r什么时候++(--)

对于使区间变大的操作(即l--,r++)我们应先使l--,r++后再进行后续的操作(因为这样我们操作的数才是我们真正添加进来的数)

对于使区间变小的操作(即l++,r--)我们应先进行操作后使l++,r--(因为这样我们操作的数才是我们真正删除的数)

绿色是要加入(删除的数),黄色是此时的r指针,自己对着理解一遍吧

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
struct xxx{
int l,r,id,block;
}q[];
int cnt[],a[];long long ans[];
bool cmp(xxx a,xxx b){if(a.block!=b.block)return a.block<b.block;return a.r<b.r;}
int main()
{
int n,m,k;scanf("%d%d%d",&n,&m,&k);int T=(int)sqrt((double)n);
for(int i=;i<=n;i++)scanf("%d",&a[i]);
for(int i=;i<=m;i++)
{
scanf("%d%d",&q[i].l,&q[i].r);q[i].id=i;q[i].block=(q[i].l+)/T;
}
sort(q+,q+m+,cmp);
int l=,r=;long long sum=;
for(int i=;i<=m;i++)
{
while(r<q[i].r){sum=sum+*cnt[a[++r]]+;cnt[a[r]]++;}
while(r>q[i].r){sum=sum-*cnt[a[r]]+;cnt[a[r--]]--;}
while(l<q[i].l){sum=sum-*cnt[a[l]]+;cnt[a[l++]]--;}
while(l>q[i].l){sum=sum+*cnt[a[--l]]+;cnt[a[l]]++;}
ans[q[i].id]=sum;
}
for(int i=;i<=m;i++)printf("%lld\n",ans[i]);
return ;
}

那如果区间操作不只有查询,还有修改呢?(以下3√n表示3次根号n)(注意,莫队的修改只能单点修改)

以下记题目给出的原始数组为a数组

我们把所有操作按发生时间假想成一个时间轴,绿色表示修改操作,红色表示查询操作

我们可以发现,对于每个查询操作,它只和在它之前发生的修改操作有关

我们考虑用x[i]表示第i个查询操作前进行了多少次修改,然后再用一个变量now记录当前进行了多少次修改,这样我们就能计算出要还原(新增)几次修改。

对于修改,我们要记录它的几个要素,pre:修改前的值(便于还原),val:修改后的值(便于修改),no:修改哪个数。

然后像暴力移动l,r指针一样,我们也暴力移动修改指针now

我们把整个数列分成T块(T=3√n),然后我们记下每个询问的左端点l所在的块blockl[i],每个询问的右端点r所在的块blockr[i],按blockl为第一关键字,blockr为第二关键字,x为第三关键字从小到大排序,依次求解就能使时间复杂度降至O(n的5/3次方)

对于每一个修改,分在当前[l,r]区间内(不是当前循环到的query的区间)和区间外考虑,区间外就直接修改a数组的值,区间内要考虑cnt的变化以及sum (当前区间内不同种类的数的个数)的加减(和l,r指针移动一样考虑)

简要说明一下带修改莫队的时间复杂度。

左右指针如上面分析,时间复杂度O(n的5/3次方)

修改指针最坏情况下对于任意两个块lblock(l在的那个块)与rblock(r在的那个块),修改指针最坏从1跑到n,而这样的情况共有3√n ^2种,所以时间复杂度O(n的5/3次方)

注意点:在读入过程中,a数组遇到修改操作也是要修改的,否则接下来的修改操作如果还有修改这一个点的话,一个pre就会记录成最初始的原数组a[]了,最后在所有操作结束后,当前被你修改的乱七八糟的数组还是要还原为a数组。

下面程序中的T是块内元素个数,不是有多少块(3√n^2)

[洛谷1903]【模板】分块/带修改莫队(数颜色)

题目描述

墨墨购买了一套N支彩色画笔(其中有些颜色可能相同),摆成一排,你需要回答墨墨的提问。墨墨会像你发布如下指令:

1、 Q L R代表询问你从第L支画笔到第R支画笔中共有几种不同颜色的画笔。

2、 R P Col 把第P支画笔替换为颜色Col。

为了满足墨墨的要求,你知道你需要干什么了吗?

输入输出格式

输入格式:

第1行两个整数N,M,分别代表初始画笔的数量以及墨墨会做的事情的个数。

第2行N个整数,分别代表初始画笔排中第i支画笔的颜色。

第3行到第2+M行,每行分别代表墨墨会做的一件事情,格式见题干部分。

输出格式:

对于每一个Query的询问,你需要在对应的行中给出一个数字,代表第L支画笔到第R支画笔中共有几种不同颜色的画笔。

输入输出样例

输入样例#1:

6 5
1 2 3 4 5 5
Q 1 4
Q 2 6
R 1 2
Q 1 4
Q 2 6
输出样例#1:

4
4
3
4

说明

对于100%的数据,N≤10000,M≤10000,修改操作不多于1000次,所有的输入数据中出现的所有整数均大于等于1且不超过10^6。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
struct xxx{
int l,r,blockl,blockr,id,t;
}data[];
struct xxx2{
int pre,val,no;
}d[];
int a[],cnt[],ans[];
bool cmp(xxx a,xxx b)
{
if(a.blockl!=b.blockl)return a.blockl<b.blockl;
if(a.blockr!=b.blockr)return a.blockr<b.blockr;
return a.t<b.t;
}
int main()
{
int n,m;scanf("%d%d",&n,&m);int T=(int)pow((double)n,0.66666666666);
for(int i=;i<=n;i++)scanf("%d",&a[i]);
int totq=,totr=;
for(int i=;i<=m;i++)
{
char c[];int l,r;
scanf("%s%d%d",c,&l,&r);
if(c[]=='Q')
{
data[++totq].l=l;data[totq].r=r;data[totq].id=totq;
data[totq].blockl=(l+)/T;data[totq].blockr=(r+)/T;data[totq].t=totr;
}
if(c[]=='R')
{
d[++totr].no=l;d[totr].val=r;d[totr].pre=a[l];a[l]=r;
}
}
for(int i=m;i>=;i--)a[d[i].no]=d[i].pre;
sort(data+,data+totq+,cmp);
int l=,r=,now=,sum=;
for(int i=;i<=totq;i++)
{
while(now<data[i].t)
{
now++;
if(d[now].no>=l&&d[now].no<=r)
{
cnt[d[now].pre]--;
if(cnt[d[now].pre]==)sum--;
cnt[d[now].val]++;
if(cnt[d[now].val]==)sum++;
}
a[d[now].no]=d[now].val;
}
while(now>data[i].t)
{
if(d[now].no>=l&&d[now].no<=r)
{
cnt[d[now].val]--;
if(cnt[d[now].val]==)sum--;
cnt[d[now].pre]++;
if(cnt[d[now].pre]==)sum++;
}
a[d[now].no]=d[now].pre;now--;
}
while(r<data[i].r){cnt[a[++r]]++;if(cnt[a[r]]==)sum++;}
while(r>data[i].r){cnt[a[r]]--;if(cnt[a[r--]]==)sum--;}
while(l<data[i].l){cnt[a[l]]--;if(cnt[a[l++]]==)sum--;}
while(l>data[i].l){cnt[a[--l]]++;if(cnt[a[l]]==)sum++;}
ans[data[i].id]=sum;
}
for(int i=;i<=totq;i++)printf("%d\n",ans[i]);
return ;
}

莫队 [洛谷2709] 小B的询问[洛谷1903]【模板】分块/带修改莫队(数颜色)的更多相关文章

  1. AC日记——【模板】分块/带修改莫队(数颜色) 洛谷 P1903

    [模板]分块/带修改莫队(数颜色) 思路: 带修改莫队: (伏地膜xxy): 代码: #include <bits/stdc++.h> using namespace std; #defi ...

  2. 【BZOJ】4129: Haruna’s Breakfast 树分块+带修改莫队算法

    [题意]给定n个节点的树,每个节点有一个数字ai,m次操作:修改一个节点的数字,或询问一条树链的数字集合的mex值.n,m<=5*10^4,0<=ai<=10^9. [算法]树分块+ ...

  3. 【BZOJ】3052: [wc2013]糖果公园 树分块+带修改莫队算法

    [题目]#58. [WC2013]糖果公园 [题意]给定n个点的树,m种糖果,每个点有糖果ci.给定n个数wi和m个数vi,第i颗糖果第j次品尝的价值是v(i)*w(j).q次询问一条链上每个点价值的 ...

  4. 洛谷 P1903 BZOJ 2120 清橙 A1274【模板】分块/带修改莫队(数颜色)(周奕超)

    试题来源 2011中国国家集训队命题答辩 题目描述 墨墨购买了一套N支彩色画笔(其中有些颜色可能相同),摆成一排,你需要回答墨墨的提问.墨墨会像你发布如下指令: 1. Q L R代表询问你从第L支画笔 ...

  5. P1903 【模板】分块/带修改莫队(数颜色)

    题目描述 墨墨购买了一套N支彩色画笔(其中有些颜色可能相同),摆成一排,你需要回答墨墨的提问.墨墨会像你发布如下指令: 1. Q L R代表询问你从第L支画笔到第R支画笔中共有几种不同颜色的画笔. 2 ...

  6. 洛谷2709 小B的询问(莫队)

    题面 题目描述 小B有一个序列,包含N个1~K之间的整数.他一共有M个询问,每个询问给定一个区间[L..R],求Sigma(c(i)^2)的值,其中i的值从1到K,其中c(i)表示数字i在[L..R] ...

  7. 洛谷.2709.小B的询问(莫队)

    题目链接 /* 数列的最大值保证<=50000(k),可以直接用莫队.否则要离散化 */ #include<cmath> #include<cstdio> #includ ...

  8. 洛谷 P1903 【模板】分块/带修改莫队(数颜色)

    题目描述 墨墨购买了一套N支彩色画笔(其中有些颜色可能相同),摆成一排,你需要回答墨墨的提问.墨墨会像你发布如下指令: 1. Q L R代表询问你从第L支画笔到第R支画笔中共有几种不同颜色的画笔. 2 ...

  9. luogu1903 【模板】分块/带修改莫队(数颜色)

    莫队算法模板 推荐阅读这篇博客 #include <algorithm> #include <iostream> #include <cstdio> #includ ...

随机推荐

  1. Python学习笔记:json模块和pickle模块(数据序列化)

    Python中的json模块和pickle都是用于数据的序列化和反序列化,它们提供的方法也是一样的:dumps,dump,loads,load dumps(obj):将对象序列化为str. dump( ...

  2. Python学习之高级特性

    切片 在Python基础篇里,我们知道Python的可序列对象可以通过索引号(下标)来引用对象元素,索引号可以由0开始从左向右依次获取,可以从-1开始由右向左获取.这种方法可以帮助我们依次获取我们想要 ...

  3. Android 做项目总结

    1.base 2.跳转可以用uihelper 3.activity和处理逻辑分开 4.userhelper保存管理用户登录信息 5.验证输入框的时候,接受的参数不要是String ,而是Edittex ...

  4. label标签的作用

    在用户注册的时候,常常用户点击文字就需要将光标聚焦到对应的表单上面,这个是怎么实现的呢?就是下面我要介绍的<label>标签的for属性 定义:for 属性规定 label 与哪个表单元素 ...

  5. Java - 收藏集 -

    Java - 收藏集 -   Java 基础思维导图,让 Java 不再难懂 - 工具资源 - 掘金思维导图的好处 最近看了一些文章的思维导图,发现思维导图真是个强大的工具.了解了思维导图的作用之后, ...

  6. 剑指Offer - 九度1504 - 把数组排成最小的数

    剑指Offer - 九度1504 - 把数组排成最小的数2014-02-06 00:19 题目描述: 输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个.例如输 ...

  7. 《Cracking the Coding Interview》——第12章:测试——题目3

    2014-04-24 23:28 题目:玩象棋游戏,你要设计一个bool型的方法来检测一个棋子能否移动到指定位置. 解法:不同的棋子有不同的移动规则,那么应该采取棋子基类实现接口,各个棋子子类来实现的 ...

  8. 【APUE】Chapter9 Process Relationships

    这一章看的比较混乱,可能是因为例子少:再有就是,这一章就是一个铺垫的章节. 9.2 terminal logins 啥叫termnial? 我感觉书上的terminal指的更可能是一些物理设备(key ...

  9. katalon系列一:初识Katalon Studio自动化测试工具

    最近准备把公司的系统搞上UI自动化,先是自己用Python+selenium+pytest写了一个框架,开始写case的时候发现效率极其慢.原因为: (1)开发为提高前端响应时间,使用前端路由技术,一 ...

  10. 【转载】Unity3D研究院之IOS自定义游戏摇杆与飞机平滑的移动

    移动开发游戏中使用到的触摸游戏摇杆在iPhone上是非常普遍的,毕竟是全触摸屏手机,今天MOMO 通过一个小例子和大家讨论Unity3D 中如何自定义一个漂亮的全触摸游戏摇杆.        值得高兴 ...