洛谷P1972 [SDOI2009]HH的项链【树状数组】

题目描述

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

输入格式

一行一个正整数 n,表示项链长度。
第二行 n 个正整数 a_i,表示项链中第 i 个贝壳的种类。
第三行一个整数 m,表示 HH 询问的个数。
接下来 m 行,每行两个整数 l,r,表示询问的区间。

1 ≤ n , m , a i ≤ 1 0 6 , 1 ≤ l ≤ r ≤ n 1≤n,m,a_i ≤10 ^6 ,1\leq l \leq r \leq n 1n,m,ai106,1lrn

输出格式

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

题目分析

首先在树状数组中将每个种类贝克第一次出现的位置+1
令nxt[i]表示与贝壳 i 种类相同的下一个贝壳的位置
对某个询问[l,r],对所有 1 ≤ k < l 1\leq k < l 1k<l将树状数组中 n x t [ k ] nxt[k] nxt[k]位置+1
这样可以保证[l,r]内每个不同数字都只有一个位置在树状数组中被加了一,由此就可以在树状数组中查询前缀和回答不同数字的个数了

而初始时将询问按左端点升序排序,将树状数组中加入nxt[k]位置的操作就是线性的次数的

#include<iostream>
#include<vector>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cstdio>
using namespace std;
#define lowbit(x) ((x)&(-x))
 
int read()
{
    int f=1,x=0;
    char ss=getchar();
    while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
    while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
    return f*x;
}

const int maxn=2000010;
int n,m;
struct Query{int ll,rr,id;}q[maxn];
int a[maxn];
int vis[maxn],nxt[maxn],last[maxn];
int sum[maxn],ans[maxn];

bool cmp(Query a,Query b){ return a.ll<b.ll;}

void add(int x){
    for(int i=x;i<=n;i+=lowbit(i)) sum[i]++;
}

int qsum(int x)
{
    int res=0;
    for(int i=x;i>0;i-=lowbit(i)) res+=sum[i];
    return res;
}

int main()
{
	n=read();
	for(int i=1;i<=n;++i)
	{
		a[i]=read();
		if(!vis[a[i]])
		{
			vis[a[i]]=1;
			add(i);
		}
	}
	
	for(int i=n;i>=1;--i)
	{
		if(last[a[i]]==0) nxt[i]=n+1;
		else nxt[i]=last[a[i]];
		last[a[i]]=i;
	}
	
	m=read();
	for(int i=1;i<=m;++i)
	{
		q[i].ll=read();
		q[i].rr=read();
		q[i].id=i;
	}
	
	sort(q+1,q+1+m,cmp);
	
	int idx=1;
	for(int i=1;i<=m;++i)
	{
		while(idx<q[i].ll) add(nxt[idx++]);
		ans[q[i].id]=qsum(q[i].rr)-qsum(q[i].ll-1);
	}
	
	for(int i=1;i<=m;++i)
	printf("%d\n",ans[i]);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值