1. 程式人生 > 其它 >Luogu P1972 [SDOI2009]HH的項鍊|樹狀陣列

Luogu P1972 [SDOI2009]HH的項鍊|樹狀陣列

題目連結

題目大意:
一個長度為 \(n\) 的序列,第 \(i\) 個數為 \(a_i\),求 \(L\)\(R\) 之間有多少個不同的 \(a_i\)
\(1 \le n,m,a_i \le 10^6\)

題解:
又是一個比較有趣的trick。以下部分借鑑於網路。
注意到對於同一區間的一個數,我們可以只關心最後出現的位置
有一個方法是我們維護每個位置出現的數是否最後一次出現。於是,求字首和便可知\([1..n]\) 的不同數的個數了。
回到本題,對於每個 \(r_i\) ,我們可以維護 \([1..r_i]\) 時的不同數及此時 \([1..l_i]\)的不同數(注意前面的區間會隨著後面數的加入而改變)。考慮到本題不強制線上,這可以通過離線

實現。

#include<bits/stdc++.h>
#define lowbit(x) x&(-x)
#define N 1001000
using namespace std;
int vis[N],t[N];
int n,a[N],m;
struct data
{
	int L,R,num,ans;
}q[N];
bool cmp(data x,data y)
{
	return x.R<y.R;
}
bool cnp(data x,data y)
{
	return x.num<y.num;
}
void add(int x,int y)
{
	for (int i=x;i<=n;i+=lowbit(i)) 
	  t[i]+=y;
}
int ask(int x)
{
	int ans=0;
	for (int i=x;i;i-=lowbit(i)) 
	  ans+=t[i];
	return ans;
}
int main()
{
	scanf("%d",&n);
	for (int i=1;i<=n;i++)
		scanf("%d",&a[i]);
	scanf("%d",&m);
	for (int i=1;i<=m;i++)
	{
		scanf("%d%d",&q[i].L,&q[i].R);
		q[i].num=i;
	} 
	sort(q+1,q+m+1,cmp);
	for (int i=1,j=1;i<=n;i++)
	{
		if (vis[a[i]]) add(vis[a[i]],-1);
		add(i,1);vis[a[i]]=i;
		while (i==q[j].R) q[j].ans=ask(q[j].R)-ask(q[j].L-1),j++;
	}
	sort(q+1,q+m+1,cnp);
	for (int i=1;i<=m;i++)
	{
		cout<<q[i].ans<<endl;
	}
	return 0;
}