zoukankan      html  css  js  c++  java
  • [SDOI2009]HH的项链

    题目描述

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

    输入输出格式

    输入格式:

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

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

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

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

    输出格式:

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

    输入输出样例

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

    说明

    数据范围:

    对于100%的数据,N <= 50000,M <= 200000。

    本题有两种方法:莫队和树状数组

    莫队:

    核心代码:

     1 void add ( int pos ) {  
     2     ++cnt[a[pos]] ;  
     3     if ( cnt[a[pos]] == 1 )   
     4         ++ answer ;  
     5 }  
     6 void remove ( int pos ) {  
     7     -- cnt[a[pos]] ;  
     8     if ( cnt[a[pos]] == 0 )   
     9         -- answer ;  
    10 }  
    11 void solve() {  
    12     int curL = 1, curR = 0 ; // current L R   
    13     for ( each query [L,R] ) {  
    14         while ( curL < L )   
    15             remove ( curL++ ) ;  
    16         while ( curL > L )   
    17             add ( --curL ) ;  
    18         while ( curR < R )   
    19             add ( ++curR ) ;  
    20         while ( curR > R )   
    21             remove ( curR-- ) ;  
    22         cout << answer << endl ;  
    23         // Warning : please notice the order "--","++" and "cur" ;  
    24     }  
    25 }  

    复杂度为N^2,要减少curL和curR指针的移动次数

    我们可以通过离线下所有的询问,然后通过某种排序,让两个指针跑动的距离尽量变少。具体的做法是把N划分成√N段,每段长度都是√N,然后在把所有询问按照L端点排序,看各个询问被划分到哪一块里。接着,对于各个划分出的段,在各自的段里,将它包含的所有区间再按照R端点排序。

    复杂度为O(N*√N)

     1 #include<iostream>
     2 #include<cstdio>
     3 #include<algorithm>
     4 #include<cstring>
     5 #include<cmath>
     6 using namespace std;
     7 struct Node
     8 {
     9     int l,r,num;
    10 }s[200001];
    11 int tim,a[50001],ans[200001],n,m,cnt[1000001],answer;
    12 bool cmp(Node a,Node b)
    13 {
    14     return ((a.l/tim)==(b.l/tim)?a.r<b.r:a.l<b.l);
    15 }
    16 void add(int x)
    17 {
    18     if (++cnt[a[x]]==1) answer++;
    19 }
    20 void remove(int x)
    21 {
    22     if ((--cnt[a[x]])==0) answer--;
    23 }
    24 int main()
    25 {int i,j,l,r;
    26     cin>>n;
    27     for (i=1;i<=n;i++)
    28     {
    29         scanf("%d",&a[i]);
    30     }
    31      cin>>m;tim=sqrt(m);
    32      for (i=1;i<=m;i++)
    33      {
    34          scanf("%d%d",&s[i].l,&s[i].r);
    35          s[i].num=i;
    36      }
    37      sort(s+1,s+m+1,cmp);
    38      l=1;r=0;
    39       for (i=1;i<=m;i++)
    40       {
    41           while (l<s[i].l)
    42           remove(l++);
    43           while (l>s[i].l)
    44           add(--l);
    45           while (r<s[i].r)
    46           add(++r);
    47           while (r>s[i].r)
    48           remove(r--);
    49           ans[s[i].num]=answer;
    50       }
    51     for (i=1;i<=m;i++)
    52     printf("%d
    ",ans[i]);
    53 }

    法2:树状数组:

    可以想到用树状数组维护区间答案,但明显,ans[i]!=sum(r)-sum(l-1);

    此题首先应考虑到这样一个结论:

    对于若干个询问的区间[l,r],如果他们的r都相等的话,那么项链中出现的同一个数字,一定是只关心出现在最右边的那一个的,例如:

    项链是:1 3 4 5 1

    那么,对于r=5的所有的询问来说,第一个位置上的1完全没有意义,因为r已经在第五个1的右边,对于任何查询的[L,5]区间来说,如果第一个1被算了,那么他完全可以用第五个1来替代。

    因此,我们可以对所有查询的区间按照r来排序,然后再来维护一个树状数组,这个树状数组是用来干什么的呢?看下面的例子:

    1 2 1 3

    对于第一个1,insert(1,1);表示第一个位置出现了一个不一样的数字,此时树状数组所表示的每个位置上的数字(不是它本身的值而是它对应的每个位置上的数字)是:1 0 0 0

    对于第二个2,insert(2,1);此时树状数组表示的每个数字是1 1 0 0

    对于第三个1,因为之前出现过1了,因此首先把那个1所在的位置删掉insert(1,-1),然后在把它加进来insert(3,1)。此时每个数字是0 1 1 0

    如果此时有一个询问[2,3],那么直接求sum(3)-sum(2-1)=2就是答案。

     1 #include<iostream>
     2 #include<cstdio>
     3 #include<algorithm>
     4 #include<cstring>
     5 using namespace std;
     6 struct Node
     7 {
     8     int l,r,num;
     9 }s[200001];
    10 int a[50001],ans[200001],n,m,c[100001],vis[1000001];
    11 bool cmp(Node a,Node b)
    12 {
    13     return (a.r<b.r||(a.r==b.r&&a.l<b.l));
    14 }
    15 int getsum(int x)
    16 {
    17     int s=0;
    18     while (x)
    19     {
    20         s+=c[x];
    21         x-=(x&(-x));
    22     }
    23     return s;
    24 }
    25 void add(int x,int d)
    26 {
    27     while (x<=n)
    28     {
    29         c[x]+=d;
    30         x+=(x&(-x));
    31     }
    32 }
    33 int main()
    34 {int i,j;
    35     cin>>n;
    36     for (i=1;i<=n;i++)
    37     {
    38         scanf("%d",&a[i]);
    39     }
    40     cin>>m;
    41     for (i=1;i<=m;i++)
    42     {
    43         scanf("%d%d",&s[i].l,&s[i].r);
    44         s[i].num=i;
    45     }
    46     sort(s+1,s+m+1,cmp);
    47     j=1;
    48     for (i=1;i<=n+1;i++)
    49     {
    50         while (j<=m&&i>s[j].r)
    51         {
    52             ans[s[j].num]=getsum(s[j].r)-getsum(s[j].l-1);
    53             j++;
    54         }
    55         if (i>n) break;
    56         if (vis[a[i]])
    57         {
    58             add(vis[a[i]],-1);
    59             vis[a[i]]=i;
    60             add(i,1);
    61         }
    62         else 
    63         {
    64             vis[a[i]]=i;
    65             add(i,1);
    66         }
    67     }
    68     for (i=1;i<=m;i++)
    69     printf("%d
    ",ans[i]);
    70 }

     

  • 相关阅读:
    Sql中联合查询中的”子查询返回的值不止一个“的问题
    关于.NET,.NET Framework 和ASP.NET的总结
    JavaScript中fn()和return fn()
    js连等赋值
    JS的Object漫想:从现象到“本质”
    javascript中的Function和Object
    javascript关于立即函数
    nodejs模块中exports和module.exports的区别
    nodejs处理url工具
    Spring学习记录(十四)---JDBC基本操作
  • 原文地址:https://www.cnblogs.com/Y-E-T-I/p/7183712.html
Copyright © 2011-2022 走看看