【题解】[hdu3874]Necklace

题目大意:给出n个序列,和m个查询每个查询为1个区间[l,r],要求对于每个查询,输出这个区间内不重复的数的和

(比如说区间内的数为 3 4 4 5,和就为12,因为对于相同的数只会统计一次)


反正这道题我第一眼看到完全是蒙的。。。后来问lz神,讲了一会才把我讲懂。。。我太弱了啊

这道题是这么做的。。。在线操作不行,考虑离线,先读入所有查询区间,然后对于值相同的数,把相邻的两个数的位置作为一个区间的左右端点。。。把查询区间与这些区间(就叫做原区间吧)一起按右端点从小到大排个序(如果右端点相同的话。。原区间排前面)

建立一颗初始值全为0的线段树。然后扫描排序后的区间,遇到一个原区间,就在他左端点的位置,加上这个原区间原来所在端点对应的值,遇到一个查询区间(左右端点为l,r),这个区间的答案就是:sum[r] - sum[l-1] - 线段树中的l~r区间和。(sum为前缀和。。。可以预处理出来)

最后再按照区间原序输出(至于这个怎么做到应该自己想一下就能搞定吧。。。)

这样为什么是对的呢?如果查询区间包含了一个 值为x的区间。。。那么通过前缀和算出来的值就多加了一个x。所以要减去x。。。如果包含了2个值为x的区间的话,那就多了两个x。。(这两个区间一定是挨着的)。。。所以线段树里面存的就是多加了的值,要减去。

至于怎么把原区间弄出来。。。用map[x]记录上一个x的位置。。。于是当扫到一个x,就有map[x]~现在位置的一个区间,然后更新map[x],参看代码

#include<cstdio>
#include<cmath>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<map>
using namespace std;
int n,m,t;
map<int,int>pre;
struct T
{
	int l,r;
	long long sum;
}tree[50000*4+10];
struct E
{
	int l,r;
	int flag;//flag==0:ori
	int num;
	bool operator <(const E &b)const
	{
		if(r!=b.r)return r<b.r;
		else return flag<b.flag;
	}
}e[300000+10];
int s[200000+10];
long long sum[50000+10];
long long ans[200000+10];
void build(int id,int l,int r)
{
	tree[id].l=l;tree[id].r=r;
	if(l==r)
	{
		tree[id].sum=0;
		return;
	}
	int mid=(l+r)>>1;
	build(id<<1,l,mid);
	build(id<<1|1,mid+1,r);
	tree[id].sum=tree[id<<1].sum+tree[id<<1|1].sum;
}
void insert(int id,int pos,int val)
{
	if(tree[id].l==tree[id].r)
	{
		tree[id].sum+=val;
	}
	else
	{
		int mid=(tree[id].l+tree[id].r)>>1;
		if(pos<=mid)insert(id<<1,pos,val);
		else insert(id<<1|1,pos,val);
		tree[id].sum=tree[id<<1].sum+tree[id<<1|1].sum;
	}
}
long long query(int id,int l,int r)
{
	if(l<=tree[id].l&&tree[id].r<=r)
	{
		return tree[id].sum;
	}
	else
	{
		int mid=(tree[id].l+tree[id].r)>>1;
		long long ret=0;
		if(l<=mid)ret+=query(id<<1,l,r);
		if(r>mid)ret+=query(id<<1|1,l,r);
		return ret;
	}
}
int main()
{
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d",&n);
		pre.clear();
		int tail=0;
		for(int i=1;i<=n;i++)
		{
			int x;
			scanf("%d",&x);
			sum[i]=sum[i-1]+x;
			if(pre.count(x))
			{
				e[tail].l=pre[x];
				e[tail].r=i;
				e[tail].flag=0;
				e[tail].num=x;
				tail++;
			}
			pre[x]=i;
		}
		scanf("%d",&m);
		for(int i=1;i<=m;i++)
		{
			int l,r;
			scanf("%d%d",&l,&r);
			e[tail].l=l;
			e[tail].r=r;
			e[tail].flag=i;
			tail++;
		}
		sort(e,e+tail);
		build(1,1,n);
		for(int i=0;i<tail;i++)
		{
			if(!e[i].flag)
			{
				insert(1,e[i].l,e[i].num);
			}
			else
			{
				ans[e[i].flag]=sum[e[i].r]-sum[e[i].l-1]-query(1,e[i].l,e[i].r);
			}
		}
		for(int i=1;i<=m;i++)
		{
			printf("%I64d\n",ans[i]);
		}
	}
	return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值