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 ;
}
 
每次做题提醒自己:题目到底有没有读懂,有没有分析彻底、算法够不够贪心、暴力够不够优雅。
原文地址:https://www.cnblogs.com/spnooyseed/p/12870904.html