牛客网暑期ACM多校训练营(第一场) J题详解

牛客网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 a 1, 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 ≤ n, q ≤ 105

* 1 ≤ ai ≤ n

* 1 ≤ li, ri ≤ n

* The number of test cases does not exceed 10.

示例1:

输入

3 2

1 2 1

1 2

1 3

4 1

1 2 3 4

1 3

输出

2

1

3

题意

输入长度为n的数列,且数列中的元素大小小于n。求[0,li]U[ri,n)区间内不同元素的个数。

解法


分析

树状数组+离线处理进行维护

我的想法

在比赛时,我想的是在输入数列时记录下每个元素的位置。接着遍历这个数组,如果这个元素第一次出现的位置小于li或者最后一次出现的位置大于ri,则不同元素的个数+1。下面是我比赛时提交的代码,但是很遗憾只通过了50%,时间复杂度还是在n^{2}

#include<iostream>
#include<vector>
using namespace std;
int main()
{
	//取消cin与stdin的同步
	ios::sync_with_stdio(false);
	int n, q, num;
	int r[10];
	vector<int>res;
	while (cin >> n >> q)
	{
		//二维vector数组,存放首位置和末位置
		vector<vector<int>> v(n);
		int k = 1;
		for (int i = 0; i < n; i++)
		{
			cin >> num;
			//向数组中加入位置
			v[num - 1].push_back(k++);
		}
		int left, right, sum = 0;
		for (int e = 0; e < q; e++)
		{
			sum = 0;
			cin >> left >> right;
			for (int i = 0; i < n; i++)
			{
				//如果数组为空 continue
				if (!v[i].size())
					continue;
				//如果首位置<left||末位置>right 个数++
				if (v[i][0] <= left || v[i].back() >= right)
					sum++;
			}
			//将个数加入数组中
			res.push_back(sum);
		}
	}
	//遍历输出
	for (auto i : res)
		cout << i << endl;
	//system("pause");
}

在我的算法中,还是遍历了整个数组。赛后看了AC的代码,其中用了树状数组,将时间复杂度从n^{2}降低到了nlog(n)。还不会树状数组的童鞋可以先看看树状数组详解

整体的思路差不多,在输入数列时,同时记录元素的首次出现的位置和最后出现的位置。输入区间后,将区间的边界值和id存放在容器中,并依据右边界升序排序,接着对树状数组进行维护即可。count数组记录没有出现的元素个数,在元素第一次出现的位置之前的count[i]值加1。result数组记录最后答案,初始值为数列中元素的个数。将result数组中的值进行维护后,即可得到答案。

#include <iostream>
#include<vector>
#include<algorithm>
using namespace std;
//用于存储左边界值和右边界值,id号
struct Region
{
	int left, right, id;
};

//重载运算符 < , 依据右边界升序
bool operator < (const Region& u, const Region& v)
{
	return u.right < v.right;
}

int main()
{
	int n, q;
	while (cin >> n >> q)
	{
		vector<int> a(n), first(n, -1), last(n), count(n), result(q);
		int total = 0;
		for (int i = 0; i < n; ++i)
		{
			cin >> a[i];
			a[i] --;
			//记录元素最后出现的位置
			last[a[i]] = i;
			//如果元素之前未出现 则记录第一次出现的位置
			if (first[a[i]] == -1)
			{
				total++;
				first[a[i]] = i;
			}
		}
		vector<Region> regions;
		for (int i = 0, l, r; i < q; ++i)
		{
			cin >> l >> r;
			//将区间和id加到容器中
			regions.push_back(Region{ l - 1, r - 1, i });
		}
		//以区间的右边界 升序排序
		sort(regions.begin(), regions.end());
		for (int i = 0, k = 0; i < n; ++i)
		{
			/*==================================================================
			树状数组维护
			记录维护区间的次数k && 此区间右边界之前的区域全部维护完成后进入循环
			===================================================================*/
			while (k < q && regions[k].right == i)
			{
				//将result数组初始化为数列中不同元素的个数
				//res引用
				int& res = result[regions[k].id] = total;
				/*===================================================
				总数- 从该区间左边界至右边界,所有不出现的元素的个数
				~j & j+1 +的优先级比&高
				=====================================================*/
				for (int j = regions[k].left; j < i; j += ~j & j + 1)
					res -= count[j];
				k++;
			}
			if (last[a[i]] == i)
			{
				/*===================================================
				在该元素左端点以前都没有出现该元素
				count数组用于统计没有出现的元素个数
				====================================================*/
				for (int j = first[a[i]] - 1; ~j; j -= ~j & j + 1)
					count[j] ++;
			}
		}
		for (auto i : result)
			cout << i << endl;
	}
	//system("pause");
}

反思与总结

我读完题的第一反应就是遍历区间,用桶记录出现的次数。但是用桶就需要遍历2次,这里的时间复杂度就是n^{2}。这样的做法肯定是不行的。接着就想到了用容器记录元素的首次出现的位置和末位置,但是最后我还是遍历了容器,时间复杂度没有减少。引入树状数组后,就可以将时间复杂度从n^{2}降低到nlog(n)。主要的难点在于树状数组的维护,利用树状数组降低时间复杂度。

展开阅读全文

没有更多推荐了,返回首页