HDU 3333 线段树 离线处理

                              Turing Tree

Time Limit: 6000/3000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 7869    Accepted Submission(s): 2862

Problem Description

After inventing Turing Tree, 3xian always felt boring when solving problems about intervals, because Turing Tree could easily have the solution. As well, wily 3xian made lots of new problems about intervals. So, today, this sick thing happens again...

Now given a sequence of N numbers A1, A2, ..., AN and a number of Queries(i, j) (1≤i≤j≤N). For each Query(i, j), you are to caculate the sum of distinct values in the subsequence Ai, Ai+1, ..., Aj.

Input

The first line is an integer T (1 ≤ T ≤ 10), indecating the number of testcases below.
For each case, the input format will be like this:
* Line 1: N (1 ≤ N ≤ 30,000).
* Line 2: N integers A1, A2, ..., AN (0 ≤ Ai ≤ 1,000,000,000).
* Line 3: Q (1 ≤ Q ≤ 100,000), the number of Queries.
* Next Q lines: each line contains 2 integers i, j representing a Query (1 ≤ i ≤ j ≤ N).

Output

For each Query, print the sum of distinct values of the specified subsequence in one line.

Sample Input

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

Sample Output

1
5
6
3
6

题目分析

我写过的大多数线段树问题都是用在线法求的,不过我想了半天都不知道如何用在线求(其实确实有方法,如果每个结点把其表示的区间内所有的叶子节点信息都存起来,并在合并区间的时候减去重复的就好,不过,这个题的数据范围是1e9,离散化后还有3e4,考虑到时间问题,这个方法就不行了)

看了别人的思路,感觉他的方法确实是有意思,我就更详细的解释一下这个方法吧。

思路:

我们采用离线法求区间和,先记录下所有查询,并按照查询区间的右端点对查询进行升序排序(后面会说明)。随后,先构造一个空树,代表这个线段树没有数据,然后我们从1到n,将数据一个个地加入线段树中。

如果在加入某个数据 val [i] = x 的时候,之前已经加入了同样的数据,我们将先加入的数据 x 从对应位置删除,再将当前的数据加入线段树。删除的话,就将之前x所在的位置的数据更新为0即可,这样就不会影响区间和了。加入数据的话,就单点更新 i 位置数据为x就好。

记执行上一段操作后此时的位置为 pos ,这样一来,我们就是在维护 [ 1 , pos] 区间内的数据,在这个区间内没有重复数据出现,那么,我们在这个区间内进行查询,所得到的区间和也会是没有重复数据的 (可以手动操作一下,应该就懂了)。

由于我们维护的区间 [ 1, pos] 内没有重复数据,那么当我们更新的位置pos等于某个查询的右端点的时候,我们执行这个查询,就可以得到查询区间内没有重复数据的区间和了,而将查询升序排序,这样是为了更方便的找到右端点等于pos的查询。

代码区

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
#include<string>
#include<cmath>
#include<fstream>
#include<vector>
#include<stack>
#include <map>
#define bug cout << "**********" << endl
//#define LOCAL = 1;
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int mod = 1e8;
const int Max = 3e4 + 10;

struct Node {
	int l, r;
	ll sum;
}node[Max << 2];

struct Query
{
	int l, r;
	int id;
	ll sum;
	bool operator<(const Query& q) const
	{
		return this->r < q.r;
	}
}question[Max << 2];

//按右端点升序
bool cmp1(const Query& q1, const Query& q2)
{
	return q1.r < q2.r;
}

//按右端点排序后,为了数据查询的结果,在对查询按编号排序,这样就可以按顺序输出了
bool cmp2(const Query& q1, const Query& q2)
{
	return q1.id < q2.id;
}


ll val[Max];
map<ll, int>m;		//映射已经出现的值和位置之间的关系

void build(int l, int r, int num)
{
	node[num].l = l;
	node[num].r = r;
	node[num].sum = 0;
	if (l == r)
		return;
	int mid = (l + r) >> 1;
	build(l, mid, num << 1);
	build(mid + 1, r, num << 1 | 1);
}

ll query(int l, int r, int num)
{
	if (l <= node[num].l && node[num].r <= r)
	{
		return node[num].sum;
	}
	int mid = (node[num].l + node[num].r) >> 1;
	ll ans = 0;
	if (l <= mid)
		ans += query(l, r, num << 1);
	if (r > mid)
		ans += query(l, r, num << 1 | 1);
	return ans;
}

void upData(int pos, ll val, int num)
{
	if (node[num].l == node[num].r)
	{
		node[num].sum += val;		//保留灵活性,可删可加,控制好val就行
		return;
	}
	int mid = (node[num].l + node[num].r) >> 1;
	if (pos <= mid)
		upData(pos, val, num << 1);
	else
		upData(pos, val, num << 1 | 1);
	node[num].sum = node[num << 1].sum + node[num << 1 | 1].sum;
}

int main()
{
#ifdef LOCAL
	freopen("input.txt", "r", stdin);
	freopen("output.txt", "w", stdout);
#endif
	int t;
	scanf("%d", &t);
	while (t--)
	{
		m.clear();
		int n;
		scanf("%d", &n);
		for (int i = 1;i <= n;i++)
		{
			scanf("%lld", val + i);
		}
		build(1, n, 1);								//构造空树
		int q;
		scanf("%d", &q);
		for (int i = 1; i <= q; i++)
		{
			int l, r;
			scanf("%d%d", &l, &r);
			question[i] = { l,r,i,0 };				//记录查询
		}
		sort(question + 1, question + 1 + q, cmp1);	//按查询的区间右端点升序排序
		int now = 1;								//记录当前询问
		for (int i = 1;i <= n;i++)					//从小到大扫一遍
		{
			if (m[val[i]] != 0)						//之前出现过的,删除,这样就保证了区间[1,i]的数据不重复
			{
				upData(m[val[i]], -val[i], 1);		//删除之前相同值的那个
			}
			m[val[i]] = i;							//更新值val[i]出现的位置,相当于覆盖了之前的位置
			upData(i, val[i], 1);					//加入新位置的数据
			while (question[now].r == i)			//更新到最近的查询区间,由于当前查询的右端点就是i,所以在区间[1,i]没有重复数据的情况下查询是满足条件的
			{
				question[now].sum = query(question[now].l, question[now].r, 1);
				//维护好了的区间[1,i]中没有重复数据,执行这个查询,得到的区间和没有重复数据
				now++;								//移动到下一个查询
			}
		}
		sort(question + 1, question + 1 + q, cmp2);	//将查询重复变回原来的顺序,方便输出
		for (int i = 1;i <= q;i++)
		{
			printf("%lld\n", question[i].sum);
		}
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值