题目描述
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;
}