zoukankan      html  css  js  c++  java
  • Galahad

    牛客练习赛52 B题
    在这里插入图片描述
    题解我是一点没看懂,就算是有之前学习的树状数组我也没看懂 ,搞的我以为我连树状数组都不会了,然后又重新学了一点基础的
    必备知识技能 : 树状数组区间单点修改 , 区间查询
    可以先看一下这个题 ___HH的项链 , 几乎是一模一样的,上面的题解更是详细

    这个题的意思非常容易读懂, 就是在给你n个数,索引1 - n , 然后呢 , 给你m个询问,问你给定区间l-r内区间和为多少,但是这个区间里面如果有重复的数,这个数只能算一次
    首先区间和可以用差分,树状数组,线段树来做,但是这个题目要求所求区间里面的重复的数只能算一次;当时就尴尬了看了半天,最后有个网友推荐说是HH的项链,然后才看懂的

    核心的观点是

    对于一个含有重复数字的区间来说,因为重复的数只算一次,那么问题就在这,到底怎么处理这个重复的数字,大佬提供的方法就是只算重复数字最右边的一个,可能没听懂,当时我也没懂,举个例子就行了

    这个是洛谷上大佬(dlhham )的讲解:

    对于若干个询问的区间[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就是答案。

    #include <iostream>
    #include <algorithm>
    #include <cstdio>
    using namespace std;
    const int N = 5e5 + 10 ;
    typedef long long ll ;
    ll c[N] , ans[N] , vis[N];
    int a[N] ;
    int n , m ;
    ll lowbit(int x)
    {
    	return x & -x ;
    }
    struct node
    {
        int l , r , id ;
    }q[N];
    typedef long long ll ;
    void update(int x , int v)
    {
    	while(x <= n)
    	{
    		c[x] += v ;
    		x += lowbit(x) ;
    	}
    }
    bool cmp(node a , node b)
    {
      if(a.r == b.r) return a.l < b.l ;
      return a.r < b.r ;
    }
    ll ask(int x)
    {
    	ll ans = 0 ;
    	for(int i = x ;i  ;i -= lowbit(i))
    		ans += c[i] ; 
    	return ans ;
    }
    int main()
    {
    	scanf("%d%d" , &n , &m) ;
        for(int i = 1 ;i <= n ;i ++)
             scanf("%d" , &a[i]) ;
        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 tt = 1 ;
        for(int i = 1 ;i <= m ;i ++)
        {
            for( ;tt <= q[i].r ;tt ++)
            {
                if(vis[a[tt]]) update(vis[a[tt]] , -a[tt]) ;
                vis[a[tt]] = tt ;
                update(vis[a[tt]] , a[tt]) ;
            }
            ans[q[i].id] = ask(q[i].r) - ask(q[i].l - 1) ;
        }
        for(int i = 1 ;i <= m ;i ++)
             printf("%lld
    " ,ans[i]) ;
    	return 0 ;
    }
     
    
    每次做题提醒自己:题目到底有没有读懂,有没有分析彻底、算法够不够贪心、暴力够不够优雅。
  • 相关阅读:
    Go语言 插入排序并返回排序前的索引
    使用patch-package定制node_modules 中的依赖包
    移动端 rem自适应布局 (750的设计稿)
    通过原型截获input.value的方法
    ts 使用 keyof typeof
    logrotate日志管理工具
    【LeetCode刷题】239.滑动窗口最大值
    【LeetCode刷题】剑指Offer 48.最长不含重复字符的子字符串
    【LeetCode刷题】912. 排序数组
    【LeetCode刷题】744. 寻找比目标字母大的最小字母
  • 原文地址:https://www.cnblogs.com/spnooyseed/p/12870904.html
Copyright © 2011-2022 走看看