HH的项链 题解(离线思想+链表+树状数组)



              本人第一篇博客重磅推出!!!


希望各位朋友以后多多捧场也多给写意见(我个人喜欢把题解写得啰嗦一点,因为这样方便理解,各位巨佬勿喷)


来讲一道提高+/省选-的骚题:HH的项链(这个HH你理解成皇后呵呵哈哈嘿嘿花花红红还是蛤蛤都可以)

                  题目来源:洛谷 P1972 [SDOI2009]HH的项链

   

题面如下:

题目描述

HH 有一串由各种漂亮的贝壳组成的项链。HH 相信不同的贝壳会带来好运,所以每次散步完后,他都会随意取出一段贝壳,思考它们所表达的含义。HH 不断地收集新的贝壳,因此,他的项链变得越来越长。有一天,他突然提出了一个问题:某一段贝壳中,包含了多少种不同的贝壳?这个问题很难回答……因为项链实在是太长了。于是,他只好求助睿智的你,来解决这个问题。

输入输出格式

输入格式:

第一行:一个整数N,表示项链的长度。

第二行:N 个整数,表示依次表示项链中贝壳的编号(编号为0 到1000000 之间的整数)。

第三行:一个整数M,表示HH 询问的个数。

接下来M 行:每行两个整数,L 和R(1 ≤ L ≤ R ≤ N),表示询问的区间。

输出格式:

M 行,每行一个整数,依次表示询问对应的答案。

数据范围:

对于100%的数据,n<=500000,m<=200000

------------------------------------------------------------------------------------------------------------------------------------------------------

注:以下内容中的点表示的就是贝壳

题意分析:区间统计点的种类数

看到这题的数据范围应该就可以判断出暴力统计是不可行的,因为那样时间复杂度将会是O(mn)肯定会超时

而想要快速得区间统计很自然得就会想到线段树、树状数组什么之类的

但是比较恶心的是这道题的题意是要统计种类数,不过可以想到把第一次出现的种类的点(贝壳)的值赋为1,否则赋为0,这样统计种类只要区间求和即可

但是每个点在询问的区间不同是会提供不同的结果,这可要命了QAQ

所以我们需要稍稍改一下思路,看到它是区间询问,就可以联想到可以把L作为第一关键字,R作为第二关键字把询问进行从小到大的排序(即先按L从小到大排序,L相同时按R从小到大排序),这和莫队有些类似,需要离线实现(也就是在读入完所有询问再处理而不是读入一个处理一个)

这样就会方便我们修改点的值,因为现在保证询问的区间左端点是递增的,所以每次当前询问的左端点L1移动到排完序的下一个询问的左端点L2时(此时L2>=L1肯定成立),从第L1个点一直跑到第(L2-1)个点(对,第L2个不能包括,因为它在新询问的区间范围之内),很显然这些点在之后的询问中不会再访问到,所以这些点中如果有是第一个该种类的点(也就是这个点点值是1),那么把下一个与它种类相同的点的点值(改之前肯定是0)改为1,(这里可优化,见下文优化①)因为这个新的点在新的询问的区间中就会是第一个该种类的。

然后用支持快速区间求和算法搞定(我个人比较喜欢尽量用树状数组,因为好写),顺便说一下,由于树状数组的区间求和是类似于求两个前缀和相减得到的,所以虽然在每次左端点移动时两个左端点间每次找到的点值为1的点地位已经不是该种类第一个点了,因为之后询问的区间内根本不会有它,可正是这样,它的点值其实不用改,它的点值在前缀和相减时会被抵消掉,影响不到之后的结果

实现是这样的,先开一个数组a,a[i]表示第i个贝壳的种类,还有数组b存点值,接着是数组t,t[i]存的是种类为i的贝壳当前最后一次出现的位置,用于搞next(后面会讲),初值为0,然后是数组c是维护数组b的树状数组,再开一个next数组,next[i]表示下一个种类与位置在i的贝壳相同的贝壳的位置,初值为500001(即n的最大值+1),这样如果在两个左端点间找到一个贝壳是该种类的最后一个时,就会把第500001个点的点值改为1(这个点其实不存在所以不用担心),在输入时处理一个新贝壳(设位置为i,种类为x),如果t[x]为0,那么这个贝壳就是它的种类的第一个,所以就b[i]=1;t[x]=i;如果t[i]不为0,就next[t[x]]=i;t[x]=i;然后还要开一个结构体用于存储询问,可以这样:

struct ask{

int l;//左端点

int r;//右端点

int address;//询问的位置,因为在排完序后询问的顺序就与原来不同了,所以要把询问输入时的顺序存下来

}hh[200003];

还需要一个ans数组存结果,在每次算完排完序后的第i个询问时就存储在ans[hh[i].address]里,输出结果时正序输出ans数组即可

然后写一个眉清目秀的树状数组,再按上文的意思写就行

优化①:再讲一个优化,在每次左端点移动时两个左端点(L1,L2,且L1<=L2)间找到找到的点值为1的点,即将把下一个与该点同种类的点的点值改为1时,在寻找要修改的点时可以优化,因为如果我们找到的要修改的点也在两个左端点之间,那么它肯定还会被访问到并且再继续修改下一个,有点浪费时间,不如我们这样,如果寻找到的这个点所在的位置小于L2,那么我们就不要改这个点,直接继续找下一个同种类的点,直到找到的点的位置大于等于L2为止(特殊情况就是当找到了一个是它的种类的最后一个的贝壳时,下次就会找到“第500001个点”,它的位置自然会大于L2,也一样可以停止),然后再修改点值,可以用一个while循环实现:

for(int j=hh[i].l;j<=hh[i+1].l-1;j++){

  if(b[j]==1){

  int p=j;

  while(j<hh[i+1].l)

       p=next[p];

  b[p]=1;

  add(p,1);

  }

}

好了,该讲的讲完了,再贴一下我的代码:

#include<bits/stdc++.h>
#define rep(i,n) for(register IL i=1;i<=n;i++)
#define IL int  //本题需要的数据类型 
using namespace std;
const IL M=500003;
IL n,m,a[M],b[M],c[M],t[1000001],x,limit,ans[200001],next[M];
struct ask{
  int l,r,address;
}hh[200001];
bool cmp(ask a,ask b){
  return (a.l<b.l||(a.l==b.l&&a.r<b.r));

IL lowbit(IL x){//返回x在二进制下的1的最低位 
  return x&-x; 
}
IL add(IL k,IL delt){//在位置k上加上delt
  while(k<=limit){
  c[k]+=delt;
  k+=lowbit(k);
  }
}
IL getsum(IL k){//求编号1到k的点的值的和(相当于前缀和) 
  IL total=0;
  while(k>0){
  total+=c[k];
  k-=lowbit(k);
  }
  return total;
}
int main(){
  scanf("%d",&n);
  limit=n;
  rep(i,n)
  {
    next[i]=500001;
    scanf("%d",&x);
    if(!t[x]){
 b[i]=1;
 add(i,1);
}
    else
      next[t[x]]=i;
    t[x]=i;
  }
  scanf("%d",&m);
  rep(i,m){
  scanf("%d %d",&hh[i].l,&hh[i].r);
  hh[i].address=i;
  }
  sort(hh+1,hh+m+1,cmp);
  rep(i,m-1){
  ans[hh[i].address]=getsum(hh[i].r)-getsum(hh[i].l-1);
  for(int j=hh[i].l;j<=hh[i+1].l-1;j++){
 if(b[j]==1){
  int p=j;
  while(p<hh[i+1].l){
          p=next[p];
}
        b[p]=1;
        add(p,1);
 }
}
  }
  ans[hh[m].address]=getsum(hh[m].r)-getsum(hh[m].l-1);
  rep(i,m)
    printf("%d\n",ans[i]);
  return 0;

猜你喜欢

转载自blog.csdn.net/qq_34304750/article/details/80719833