【洛谷】P1972 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 <= 500000,M <= 500000。

 

首先是莫队算法,这题数据貌似被加强过了,莫队算法也AC不了

戳此看莫队算法良心讲解

60分代码:

#include<iostream>
#include<sstream>
#include<algorithm>
#include<string>
#include<cstring>
#include<iomanip>
#include<vector>
#include<cmath>
#include<ctime>
#include<stack>
#include<queue>
#include<map>
#define mem(a,b) memset(a,b,sizeof(a))
#define e 2.71828182
#define Pi 3.141592654
using namespace std;
struct node
{
	int l,r,block,order;
	friend bool operator < (node& a,node& b)//分块排序 
	{
		if(a.block!=b.block) return a.block<b.block;
		else return a.l<b.l;
	}
}enq[500010];
int res=0,cnt[1000010],a[500010],ans[500010];
inline void add(int x)
{
	if(!cnt[a[x]]) res++;
	cnt[a[x]]++;
}
inline void del(int x)
{
	if(cnt[a[x]]==1) res--;
	cnt[a[x]]--;
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(NULL);cout.tie(NULL);
	int N;
	cin>>N;
	for(int i=1;i<=N;i++) cin>>a[i];
	int M,b=sqrt(N);
	cin>>M;
	for(int i=1;i<=M;i++)
	{
		enq[i].order=i;
		cin>>enq[i].l>>enq[i].r;
		enq[i].block=enq[i].l/b;
	}
	mem(ans,0);
	mem(cnt,0);
	sort(enq+1,enq+M+1);
	
	int l=0,r=0;
	for(int i=1;i<=M;i++)
	{
		int ll=enq[i].l,rr=enq[i].r;
		while(l<ll)  del(l++);
		while(l>ll)  add(--l);
		while(r<rr)  add(++r);
		while(r>rr)  del(r--);
		ans[enq[i].order]=res;
	}
	
	for(int i=1;i<=M;i++)
	cout<<ans[i]<<endl;
}

 

树状数组做法:(浅蓝色字体转自https://www.luogu.org/blog/user3432/solution-p1972

这个题用树状数组,线段树等等都可以做,不过用树状数组写起来更方便。

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

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

 

AC代码:

#include<iostream>
#include<sstream>
#include<algorithm>
#include<string>
#include<cstring>
#include<iomanip>
#include<vector>
#include<cmath>
#include<ctime>
#include<stack>
#include<queue>
#include<map>
#define mem(a,b) memset(a,b,sizeof(a))
#define e 2.71828182
#define Pi 3.141592654
#define lowbit(x) (x&(-x))
using namespace std;
struct node
{
	int l,r,order;
	friend bool operator < (node& a,node& b)//按右端点升序排列 
	{
		return a.r<b.r;
	}
}enq[500010];
int N,a[500010],tree[500010],last[1000010],pre[500010],ans[500010];
//last[i]表示颜色i最后一次出现的位置,pre[i]表示第i个贝壳相同颜色前面最近一次出现的位置 
void update(int k,int v)
{
	if(k==0) return;//不加这条的话将会,,,死循环 
	for(int i=k;i<=N;i+=lowbit(i))
	tree[i]+=v;
}
int getsum(int k)
{
	int res=0;
	for(int i=k;i>0;i-=lowbit(i))
	res+=tree[i];
	return res;
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(NULL);cout.tie(NULL);
	cin>>N;
	for(int i=1;i<=N;i++) 
	{
		cin>>a[i];
		pre[i]=last[a[i]];
		last[a[i]]=i;
	}
	int M;
	cin>>M;
	for(int i=1;i<=M;i++)
	{
		enq[i].order=i;
		cin>>enq[i].l>>enq[i].r;
	}
	mem(tree,0);
	sort(enq+1,enq+M+1);
	
	int rr=0;
	for(int i=1;i<=M;i++)
	{
		while(rr<enq[i].r)//求从1到enq[i].r间的颜色种类 
		{
			rr++;
			update(pre[rr],-1);//删除原来位置的,如果原来位置为0,则不必操作
			update(rr,1); //增加当前位置的
		}
		ans[enq[i].order]=getsum(enq[i].r)-getsum(enq[i].l-1);//前缀和思想 
	}
	for(int i=1;i<=M;i++)
	cout<<ans[i]<<endl;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值