牛客多校第一場: J-Different Integers【樹狀數組】

 J.Different Integers

時間限制:C/C++ 2秒,其他語言4秒
空間限制:C/C++ 524288K,其他語言1048576K
64bit IO Format: %lld

題目描述

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.

輸入

3 2

1 2 1

1 2

1 3

4 1

1 2 3 4

1 3

輸出

2

1

3

 

題意

求題目給出區間以外出現的不同數字個數(包含端點)

思路

將區間倍增轉換爲求連續區間不同數字個數,通過統計查詢範圍內first數組1的個數得到答案。用樹狀數組能夠很方便的得出答案,同時降低時間複雜度,用普通的嵌套循環會TLE。

AC代碼

#include <bits/stdc++.h>

using namespace std;

const int MAX = 100005;
const int DMAX = 2 * MAX;
//儲存區域的左右邊以及區域輸入順序
struct note
{
	int l;
	int r;
	int no;
}area[MAX];

//儲存數組
int a[DMAX];
//儲存i位置上的數是否是第一次出現
int first[DMAX];
//儲存i位置上的數的下一個出現的位置
int nextn[DMAX];
//儲存數i逆序遍歷時上一次的位置
int logn[MAX];
//儲存first的樹狀數組
int c[DMAX];
//儲存區域出現的次數
int result[MAX];

//===============================================
//樹狀數組處理相關

//求參數的二進制最低位表示的十進制數
int lowbit(int x)
{
	//返回十進制數x的二進制形式中最右邊的1代表的十進制數
	return x & -x;
}


//樹狀數組更新後綴和
void update(int x, int val)
{
	while (x < DMAX) 
	{
		c[x] += val;
		x += lowbit(x);
	}
}


//樹狀數組查詢前綴和
int sum(int x)
{
	int s = 0;
	//此算法可得出樹狀數組c的原數組a從a[1]至a[x]的和
	while (x) 
	{
		s += c[x];
		x -= lowbit(x);
	}
	return s;
}

//===============================================

//sort函數自定義排序方式
bool cmp(note a, note b)
{
	//按重新定位的左端點升序排序
	return a.l < b.l;
}


int main()
{
	//數組大小
	int n;
	//詢問次數
	int q;
	int i, j;

	while (scanf("%d%d", &n, &q) != EOF)
	{

		//初始化處理
		memset(a, 0, sizeof(int)*DMAX);
		memset(nextn, 0, sizeof(int)*DMAX);
		memset(logn, 0, sizeof(int)*MAX);
		memset(result, 0, sizeof(int)*MAX);
		memset(c, 0, sizeof(int)*DMAX);

		for (i = 1; i <= n; i++)
		{
			scanf("%d", &a[i]);
			a[n + i] = a[i];
			//first數組在此處初始化爲1,之後在逆序遍歷時逐個改爲0
			first[i] = 1;
			first[n + i] = 1;
		}

		//維護first數組和nextn數組

		for (i = 2 * n; i > 0; i--)
		{
			nextn[i] = logn[a[i]];
			//上一個數出現的位置上的first置0
			first[logn[a[i]]] = 0;
			//更新上一個數(此時是該數)的位置
			logn[a[i]] = i;
		}

		//維護first的樹狀數組

		for (i = 1; i <= 2 * n; i++)
		{
			if (first[i])update(i, 1);
		}

		//核心查詢代碼:
		//輸入查詢區間,左右換位並排序

		for (i = 1; i <= q; i++)
		{
			scanf("%d%d", &area[i].l, &area[i].r);
			area[i].no = i;
			area[i].l = n + area[i].l;
			swap(area[i].l, area[i].r);
		}
		sort(area + 1, area + q + 1, cmp);
		//注意此處不能漏掉+1,因爲area是從1開始記錄的(經嘗試去1後也AC)

		//樹狀數組離線查詢並計數

		for (i = 1, j = 1; i <= 2 * n&&j <= q;)
		{
			//如果該數在此範圍內(用其是否處在此範圍開頭判斷)
			if (i == area[j].l)
			{
				//該數在l-r範圍內有無出現過
				result[area[j].no] = sum(area[j].r) - sum(area[j].l) + 1;
				j++;
			}
			else
			{
				if(first[i])
				{
					//將該數第一次出現的1標記向後移,同時更新樹狀數組
					first[i] = 0;
					//update傳輸的第一個參數爲更改的位置,第二個參數爲更改值(非更改後的值)
					update(i, -1);
					first[nextn[i]] = 1;
					update(nextn[i], 1);
				}
				i++;
			}
			//此處真正起到統計作用的爲對範圍進行遍歷的兩個sum函數相減,得出了在此區間內第一次出現的1數
			//對數組的遍歷只是起到將數組中第一次出現的1“趕”到範圍內便於統計,同時判斷是否開始遍歷範圍
			//對範圍和數組的同時遍歷省去了不必要的嵌套循環,降低了時間複雜度
		}


		for(i=1;i<=q;i++)
		{
			printf("%d\n", result[i]);
		}
	}
	return 0;
}


第一次集訓做牛客多校賽,一開始用的笨方法嵌套循環,只過了50%。之後研究學長的題解和AC代碼,學習了樹狀數組用法,今後還要多刷題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章