hdu4638 Group

hdu4638
题意:第一行输入一个t。t组数据.
第二行n,m。n代表n个数字.m代表询问m次。
第三行输入n个数字.
接下来m行。每行l,r。代表需要查询的区间。
查询的是l-r区间内有几段连续的数字。
eg:1-5 有数字3 1 2 5 4.排一下序:1 2 3 4 5.连续。所以输出1。
2-4 有数字1 2 5 4.排一下序:1 2 5,1 2连续,5连续。所以输出2。
思路如CTX说所。对于区间内的查询,我只知道4种算法:线段树,树状数组,RMQ,莫队。
我搜了题解,有用树状数组和线段树离线处理做的,也有用莫队做的。只说一下莫队的解法。

(区间求和查询貌似莫队也可以处理)莫队算法是解决区间问题的。主要思想是知道R-L区间的答案后。能推出R-(L+1)或者R-(L-1)
或(R-1)-L 或(R+1)-L。推出区间的时间基本为O(1)。以这个题为例。看样例。比如我们知道了
2-4区间的ans。我们来推1-4.我们向左移动一位,多了一个3,我们如何判断当3加入到2-4区间后ans的变化。
我们看vis[3-1]和vis[3+1](vis[2],vis[4])是否出现过。如果都出现过那么ans–;因为3将左右数字连接
了起来。如果只出现过一个,那么不用变。如果一个也没出现过。那么ans++;因为多了一个数字。同理当我们
将左区间右移的时候,同样规律。那么很明显了,如果这个点在R–L区间那么vis[num[R–L]]=1;这是递推的
思路。

接下来我们说莫队。其实仔细想想,如果这是这么XJB暴力枚举区间的话,其实时间复杂度不会低到哪。(挺高
的…)接下来我们再想,我们是递推区间的。那么自然的就会想到排序。有很多这样的思路用排序来降低时间
复杂度。所以莫队引入了分块,和排序。我们设blcok=sqrt(n);be数组为数字编号i在哪一块。

for(int i=1;i<=n;i++) scanf("%d",&num[i]),be[i]=i/block+1;

接着对询问排序,以及分块

bool cmp(mo a,mo b){return be[a.l]==be[b.l]?a.r<b.r:a.l<b.l;}

为什么这样做emmm.因为可以降低时间复杂度..至于为什么会降低..借鉴博客
https://www.cnblogs.com/Paul-Guderian/p/6933799.html
·时间复杂度分析(分类讨论思想):

首先,枚举m个答案,就一个m了。设分块大小为unit。

分类讨论:

①l的移动:若下一个询问与当前询问的l所在的块不同,那么只需要经过最多2*unit步可以使得l成功到达目标.复杂度为:O(m*unit)

②r的移动:r只有在Be[l]相同时才会有序(其余时候还是疯狂地乱跳,你知道,一提到乱跳,那么每一次最坏就要跳n次!),Be[l]什么时候相同?在同一块里面l就Be[]相同。对于每一个块,排序执行了第二关键字:r。所以这里面的r是单调递增的,所以枚举完一个块,r最多移动n次。总共有n/unit个块:复杂度为:O(n*n/unit)

总结:O(n*unit+n*n/unit)(n,m同级,就统一使用n)

根据基本不等式得:当n为sqrt(n)时,得到莫队算法的真正复杂度:

O(n*sqrt(n))

最后说一下代码的实现

#include<stdio.h>
#include<string>
#include<queue>
#include<math.h>
#include<map>
#include<stack>
#include<string.h>
#include<iostream>
#include<algorithm>
#define PI acos(-1.0)
typedef long long ll;
using namespace std;
#define mem(a,b) memset(a,b,sizeof(a))
const int N=1e5+100;
struct mo{int l,r,id;}q[N];//创建结构体
int n,m,num[N],block,be[N],ans,vis[N],t,lans[N];//block是分块大小,be是序号i在哪个块,vis标记数组。lans最后存答案的数组
bool cmp(mo a,mo b){return be[a.l]==be[b.l]?a.r<b.r:a.l<b.l;}//对m次询问分块排序
void calu_1(int pos){int sum=vis[num[pos]-1]+vis[num[pos]+1];if(sum==2)ans++;if(!sum)ans--;vis[num[pos]]=0;}//缩小区间计算的ans
void calu_2(int pos){int sum=vis[num[pos]-1]+vis[num[pos]+1];if(sum==2)ans--;if(!sum)ans++;vis[num[pos]]=1;}//扩大区间计算的ans
int main()
{
    scanf("%d",&t);
    while(t--)
    {
      memset(vis,0,sizeof(vis));
      scanf("%d%d",&n,&m);block=sqrt(n);
      for(int i=1;i<=n;i++) scanf("%d",&num[i]),be[i]=i/block+1;//分块
      for(int i=1;i<=m;i++) scanf("%d%d",&q[i].l,&q[i].r),q[i].id=i;
      sort(q+1,q+m+1,cmp);//离线处理,对询问分块排序
      int l=1,r=0;ans=0;//初始化
      for(int i=1;i<=m;i++)//这里稍微有一点坑,不能直接套莫队的板子过来。如果直接套了板子过来(如果不是请无视),如样例,将L从1右移到2,之后r从1右移到4.先取消了1,再标记1.其实1没有用。所以将第一行移到最后即可。
      {                                   
          while(l>q[i].l) calu_2(l-1),l--;//扩大区间,左区间左移
          while(r<q[i].r) calu_2(r+1),r++;//扩大区间,右区间右移
          while(r>q[i].r) calu_1(r),r--;//缩小区间,右区间左移
          while(l<q[i].l) calu_1(l),l++;//缩小区间,左区间右移
          lans[q[i].id]=ans;//记录答案
      }
      for(int i=1;i<=m;i++)//输出
        cout<<lans[i]<<endl;
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/zxy160/article/details/79992503