HDU-3333-Turing Tree-离散化思想

时间:2021-01-24 18:26:29

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3333

这个题目很好!!!真的很好!!!思维能力的锻炼!!!

                题目意思:给你个数组,然后多次查询,每次查询一个区间,叫你求这个区间内所有不重复的数字之后;数据量很大,想用暴力是不可能的;

                       思路:线段树+离线处理;也就是说我先把你每次要查询的区间保存下来,并且将他们按右区间由小到大排好序,同时我们也保存题目提供的数组,

                                  而且不暂时不更新到线段树里面,这里我们就得换个角度去想了,就这么简单的更新到树里面,那算得结果肯定是错误的啊,因为前面加过了;

 

 

                                  我们形象的来说一下,我每次更新一个数字,我判断前面有没有出现过,如果有出现过,我就将前面的那个数字清零,并且将这个数字存在最后一次

                                  出现的位置;但是这样有的人就会说,这样岂不是会改变后面的结果吗?这里就有一个技巧了,那就是我代码里面提到的那样,把区间先排序,这样我每次

                                  更新到区间最靠左的那个区间,就直接先把结果算出来,后面在更新,然后再清零,这样对我数据就没有影响;

 

呼呼,说的有些啰嗦,好像还不是很清楚;可以结合我的代码加注释,理解一下;也可以结合一下这个链接里说的思路,还清晰;

链接:http://www.cnblogs.com/deadblue/archive/2012/09/13/2683199.html

求助一下大家,就是我那个为什么用lower_bound()函数会WA,而用我代码里的二分查找就可以AC,结果是一样的啊,希望可以不吝赐教;感谢。。。ORZ...

#include<iostream>
#include<string>
#include<cstdio>
#include<cstring>
#include<queue>
#include<map>
#include<set>
#include<vector>
#include<algorithm>
#define LL long long
using namespace std;
const int N=30015;
const int M=100015;
struct node
{
int l,r;
LL v;
}node[N<<2];
struct NODE // 存储查询的区间,及下标;
{
int l,r,id;
}temp[M];
void PushUp(int numb) // 向上更新父节点数据;
{
node[numb].v=node[numb<<1].v+node[numb<<1|1].v;
}
void build(int l,int r,int numb) // 建立线段数
{
node[numb].l=l;
node[numb].r=r;
node[numb].v=0;
if(l==r) return;
int mid=(l+r)>>1;
build(l,mid,numb<<1);
build(mid+1,r,numb<<1|1);
}
void Insert(int numb,int t,int v) // 数据的更新,插入;
{
int l=node[numb].l,r=node[numb].r;
if(l==r){
node[numb].v+=v;
return;
}
int mid=(l+r)>>1;
if(t>mid) Insert(numb<<1|1,t,v);
else if(t<=mid) Insert(numb<<1,t,v);
PushUp(numb);
}
LL query(int l,int r,int numb) // 区间查询;
{
if(l==node[numb].l&&r==node[numb].r) return node[numb].v;
int mid=(node[numb].l+node[numb].r)>>1;
if(l>mid) return query(l,r,numb<<1|1);
else if(r<=mid) return query(l,r,numb<<1);
else return query(l,mid,numb<<1)+query(mid+1,r,numb<<1|1);
}
bool cmp(NODE x,NODE y) // 自定义结构体排序;
{
return x.r<y.r;
}
int a[N],b[N],bb[N],visit[N];
LL ans[M];
int Is(int v,int k){ // 二分查找,其中v是要找的数,k表示数组的个数;
int mid,l=1,r=k;
while(l<=r){
mid=(l+r)/2;
if(bb[mid]>v) r=mid-1;
else if(bb[mid]<v) l=mid+1;
else return mid;
}
return 0;
}
int main()
{
int t,m,n;
scanf("%d",&t);
while(t--){
scanf("%d",&n);
build(1,30015,1); // 建树;
for(int i=1;i<=n;i++){
scanf("%d",&a[i]); // 先将数据用数组保存;
b[i]=a[i]; // 为了不破坏数据的顺序,再用一个数组,
}
sort(b,b+n+1);
bb[1]=b[1];
int k=1;
for(int i=2;i<=n;i++){ // 去重;
if(b[i]!=b[i-1]){
bb[++k]=b[i];
}
}
memset(ans,0,sizeof(ans)); // 初始化;
memset(visit,0,sizeof(visit));
scanf("%d",&m);
for(int i=1;i<=m;i++){ // 先将要查询的数据用结构体保存;
scanf("%d%d",&temp[i].l,&temp[i].r);
temp[i].id=i; // 保存下标,这样排序后仍能够和结果相对应保存;
}
sort(temp,temp+m+1,cmp); // 按右区间由小到大排序;
int j=1;
for(int i=1;i<=n;i++){
//int id=lower_bound(bb,bb+n+1,a[i])-bb; // 第一个大于等于值a[i]的位置,用这个函数提交就WA,想了一下午也没有想明白为什么,
int id=Is(a[i],k); // 返回该元素下标; 用这个写的二分查找就可以AC,ORZ...如果谁懂希望可以教我。。。跪求
if(visit[id]){ // 如果之前出现过,那么就将前面的清零;因为我们是计算区间不重复数字的和,所以对结果没有影响;
Insert(1,visit[id],-a[i]);
}
Insert(1,i,a[i]);
visit[id]=i; // 标记出现过,并且记录出现的位置;
for(;j<=m;j++){
if(i==temp[j].r){ // 这就是我们为什么要讲区间排序的原因,因为只有这样我们才可以保证在清零之后不影响结果;所以将
LL sum=query(temp[j].l,temp[j].r,1); // 这个查询放在插入里面,只要插入的点有我要查找的右区间,
ans[temp[j].id]+=sum; // 我就立马算出结果,避免后面出现的数字把之前的数据清零;
}else break;
}
}
for(int i=1;i<=m;i++) printf("%lld\n",ans[i]);
}
return 0;
}