1. 程式人生 > >牛客網暑期ACM多校訓練營(第一場)J Different Integers(樹狀陣列 + 離線 或 莫隊)

牛客網暑期ACM多校訓練營(第一場)J Different Integers(樹狀陣列 + 離線 或 莫隊)

題目描述 

Given a sequence of integers a1, a2, ..., an and q pairs of integers (l1, r1), (l2, r2), ..., (lq, rq), find count(l1, r1), count(l2, r2), ..., count(lq, rq) where count(i, j) is the number of different integers among a1, a2, ..., ai, aj, aj + 1, ..., an.

輸入描述:

The input consists of several test cases and is terminated by end-of-file.
The first line of each test cases contains two integers n and q.
The second line contains n integers a1, a2, ..., an.
The i-th of the following q lines contains two integers li and ri.

輸出描述:

For each test case, print q integers which denote the result.

示例1

輸入

複製

3 2
1 2 1
1 2
1 3
4 1
1 2 3 4
1 3

輸出

複製

2
1
3

備註:

* 1 ≤ n, q ≤ 105
* 1 ≤ ai ≤ n
* 1 ≤ li, ri ≤ n
* The number of test cases does not exceed 10.

題意:給你一個數組長度為n,有q次查詢,每次查詢,給你一個區間(L,R),讓你求[1,L] + [R,n] 這個中有多少不同的數字;

當時做這道題時,想著是莫隊,感覺不穩,隊友說用 樹狀陣列 +離線能寫

思路: 因為詢問的是 [1,L] + [R,n]中不同數字的個數,我們先用倍增法,把長度為n的陣列,擴增到 2*n,問[1,L] + [R,n]之間不同數的個數,其實就是問[R,n+L] 中不同數的個數;

把詢問區間按右端點從小到大排序,離線操作,從1 操作到 shu[i].r,操作過程中,記錄a[j]上一次出現的位置,當操作這個位置時,這個位置的值是a[j],把這個位置上 +1,把上一次出現 a[j]的位置 -1,操作到當前位置時,在1~當前位置這個區間中,保證每個數計數都是在最後一次出現的位置上。

個人感覺:當離線操作時,按左端點排序時,一般都是樹狀陣列先從前往後初始化了一邊了,再從1~shu[i].l消除影響,當按右端點排序時,一般樹狀陣列中沒有從先往後初始化。請注意我的措辭,一般情況下,但還有具體情況具體分析;

程式碼:

#include<bits/stdc++.h>
using namespace std;
#define Max 2*100005

int a[Max];   // 求[1,L]+[R+n]中不同數的數量時,把所給的區間倍增;
			 //就是求[R,L+n]中的不同數的數量;  
int c[Max];
int pre[Max];     // a[j], pre[a[j]],表示這個前一個a[j]出現的位置; 
int anss[Max]; 
int n,q;
struct node
{
	int pi;
	int l,r;
}stu[Max];

int cmp(node a,node b)
{
	return a.r<b.r;
}
int lowbit(int x)
{
	return x&-x;
}
void add(int x,int k)
{
	while(x<=n)
	{
		c[x] += k;
		x += lowbit(x);
	}
}
int sum(int x)
{
	int ans = 0;
	while(x>0)
	{
		ans += c[x];
		x -= lowbit(x);
	}
	return ans;
}
int main()
{
	while(~scanf("%d%d",&n,&q))
	{	
		memset(c,0,sizeof(c));
		memset(pre,0,sizeof(pre));
		for(int i = 1;i<=n;i++)   // 擴增; 
		{
			scanf("%d",&a[i]);  
			a[i+n] = a[i];
		}
		int l,r;
		for(int i = 1;i<=q;i++)
		{
			scanf("%d%d",&l,&r);
			stu[i].l = r;
			stu[i].r = l+n;
			stu[i].pi = i;
		}
		n<<=1;
		sort(stu+1,stu+q+1,cmp);
		int j = 1;
		for(int i = 1;i<=q;i++)
		{
			for(;j<=stu[i].r;j++)
			{
				if(pre[a[j]]) add(pre[a[j]],-1);   // 說明a[j]這個數前面已經出現過,把上一次出現a[j]位置上的記錄消除; 
				add(j,1);              // 把當前位置上,記錄; 
				pre[a[j]] = j;        //記錄上一個a[j] 出現的位置; 
			}
			anss[stu[i].pi] = sum(stu[i].r) - sum(stu[i].l-1);
			// 當從1 操作到 shu[i].r時,數狀陣列中存時,[1,shu[i].r]中不同數的個數;
			//而sum(stu[i].l-1) ,存的是[stu[i].l,stu[i].r] 中沒有出現的數 的不同數的個數; 
		}
		for(int i = 1;i<=q;i++)
			printf("%d\n",anss[i]);
	}
	return 0;
}