牛客多校第一场: 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代码,学习了树状数组用法,今后还要多刷题。

猜你喜欢

转载自blog.csdn.net/x948675238/article/details/81138278