zoukankan      html  css  js  c++  java
  • Different Integers(树状数组)

    描述

    传送门:我是传送门

    Given a sequence of integers a1,a2,,ana1,a2,…,an and qq pairs of integers (l1,r1),(l2,r2),,(lq,rq)(l1,r1),(l2,r2),…,(lq,rq), find count(l1,r1),count(l2,r2),,count(lq,rq)count(l1,r1),count(l2,r2),…,count(lq,rq)where count(i,j)count(i,j) is the number of different integers among a1,a2,,ai,aj,,ana1,a2,…,ai,aj,…,an.

    输入

    The input consists of several test cases and is terminated by end-of-file. The first line of each test cases contains two integers nn and qq. 

    The second line contains n integers a1,a2,,ana1,a2,…,an. The i-th of the following qq lines contains two integers lili and riri.

    输出

    For each test case, print qq integers which denote the result.

    样例

    输入

    3 2
    1 2 1
    1 2
    1 3
    4 1
    1 2 3 4
    1 3

    输出

    2
    1
    3

    Note

    • 1n,q1051≤n,q≤105
    • 1ain1≤ai≤n
    • 1li,rin1≤li,ri≤n
    • The number of test cases does not exceed 10.

    思路

    牛客多校赛第一场的签到题,但是很遗憾当时树状数组不太会用,没能写出来,当时看其他人写的博客也是没能彻底搞懂.

    现在尝试在scaufat的题解牛客网暑期ACM多校训练营(第一场)J 题解 的基础上重新捋一下思路,添加一些代码注释

    希望能够对树状数组的灵活应用有更深的理解.

    我认为整个题目最关键的地方在于这两个地方

    • 维护一个前缀和,pre[i]表示a[1…i]有多少种不同的数字,那么对于a[l…r]的答案就为pre[r] - pre[l-1] + 在a[l…r]和a[1…l-1]同时出现的数字的种类

    • 左端每次右移的时候把对应的数的下一个位置加入到数状数组中

    代码

      1 #include <bits/stdc++.h>
      2 using namespace std;
      3 const int N = 2e5+10;
      4 #define clr(a, x) memset(a, x, sizeof(a))
      5 int n,q;
      6 int a[N];
      7 int pre[N];   // pre[i]表示数组前i个数有多少种不同的数
      8 bool vis[N];
      9 int last[N];  // 用来计算nxt数组用的辅助数组,记录每个数上一次出现的位置
     10 int nxt[N];   // 用来维护数组中每个位置的数下一次出现的位置
     11 int ans[N];
     12 
     13 struct node
     14 {
     15     int l,r,id;
     16     bool operator < (const node &b) const   // 将查询按照左端点排序
     17     {
     18         return this->l < b.l;
     19     }
     20 }query[N];
     21 
     22 int bit[N];
     23 int lowbit(int x)
     24 {
     25     return x&(-x);
     26 }
     27 
     28 void update(int x,int y)
     29 {
     30     while(x < N)
     31         bit[x] += y,x += lowbit(x);
     32 }
     33 
     34 int Query(int x)
     35 {
     36     int ret = 0;
     37     while(x)
     38         ret += bit[x],x -= lowbit(x);
     39     return ret;
     40 }
     41 
     42 int Query(int l,int r)
     43 {
     44     return Query(r)-Query(l-1);
     45 }
     46 
     47 int main()
     48 {
     49     while(scanf("%d %d",&n,&q) != EOF)
     50     {
     51         clr(bit,0); clr(vis,0); clr(nxt,0); clr(last,-1);
     52         // 输入的同时在后边复制一份
     53         for(int i = 1;i <= n;i++)
     54         {
     55             scanf("%d",&a[i]);
     56             a[i+n] = a[i];
     57         }
     58         n *= 2;
     59         
     60         // 我认为这个循环是最神奇的地方
     61         // 很强大 一个月前看的时候根本没怎么看懂
     62         pre[0] = 0;
     63         for(int i = 1;i <= n;i++)
     64         {
     65             // 处理前缀和
     66             if(!vis[a[i]])   // 如果没出现过
     67             {
     68                 vis[a[i]] = true;
     69                 pre[i] = pre[i-1]+1;   // 类似dp
     70             }
     71             else   // 如果已经出现过 
     72             {
     73                 pre[i] = pre[i-1];
     74             }
     75             // 处理nxt[]数组   last[]数组做辅助用
     76             // a[i]首次出现时不会执行此语句 【 ~(-1) = 0 】
     77             if(~last[a[i]])   
     78             {
     79                 nxt[last[a[i]]] = i;
     80             }
     81             last[a[i]] = i;
     82         }
     83         
     84         for(int i = 0;i < q;i++)
     85         {
     86             // 因为原来的代码增加query[i].l的值后又交换
     87             // 了query[i].l与query[i].r的值
     88             // 我直接在输入时交换一下l与r的值 然后增加r的值
     89             // 最终的结果是一样的
     90             scanf("%d %d",&query[i].r,&query[i].l);
     91             query[i].r += n/2;
     92             // 因为在输入完成后会对query[]保存到值按照左端点重新排序
     93             // 因此需要用query[i].id来记录一下原来的顺序,即i的值
     94             query[i].id = i;
     95         }
     96         // 按照左端点对query[]进行排序
     97         sort(query,query+q);
     98         // 从最左端的1开始向右扫描
     99         // 每次右移的时候把对应的数的下一个位置加入到数状数组中
    100         int now = 1;
    101         for(int i = 0;i < q;i++)
    102         {
    103             while(now < query[i].l)
    104             {
    105                 if(~nxt[now])
    106                 {
    107                     update(nxt[now],1);
    108                 }
    109                 ++now;
    110             }
    111             ans[query[i].id] = pre[query[i].r]-pre[query[i].l-1]+Query(query[i].l,query[i].r);
    112         }
    113         for(int i = 0;i < q;i++)
    114             printf("%d
    ",ans[i]);
    115     }
    116     return 0;
    117 }
  • 相关阅读:
    day02_07 创建新目录
    day02_04 字典
    day02_02 列表切割
    day03_01 文件操作
    MS的TREE 控件使用
    使用自定义用户控件的一些经验
    Asp.net开发心得点滴[动态加载的用户控件使用事件委托,交给页面处理的事件无效问题]
    正则表达式基础学习[1]
    自定义控件无法在VS.net编辑中显示
    错误的递归
  • 原文地址:https://www.cnblogs.com/duny31030/p/14304959.html
Copyright © 2011-2022 走看看