HDU 3874 Necklace(树状数组+离线处理)
http://acm.hdu.edu.cn/showproblem.php?pid=3874
题意:
有一个由n个整数组成的数列,要求你回答以下询问[L,R]:返回a[L]到a[R]的和。但是其中相同值的元素只能计算一次。比如数列1 1 1 2 3 4 ,如果我们求[1,3]的和,就返回1。如果我们求[2,4]的和,就返回3。
分析:
预处理:首先我们需要一个树状数组A[n],我们从左到右读入所有的a[i],如果当前a[i]=x值是第一次出现,就执行add(i,x),否则不执行。当我们扫描完了n个值后,我们保证sum(R)就是区间[1,R]的答案(想想是不是)。
接下来我们对所有查询的区间进行排序,使得[L,R]区间中L小的区间排在前面。我们首先用sum(R)给出所有区间[1,R]的查询。
接着我们要计算所有区间[2,R]的查询,但是做这步前,我们还需要消除a[1]对后面序列的影响,使得我们这个序列就好像是本来就是从a[1]开始的,从来没有存在过a[1]。要完成这个要求,我们只需要执行add(1,-a[1])并且我们需要找到a[1]值第二次出现的位置y执行add(y,a[1])。OK做完上面这步我们就保证了当前的sum(R)就是区间[2,R]的答案(有关区间[3,R],[4,R]等的查询我们目前不管,以后再说)。
所以每当我们查询完了区间[i-1,R]的答案后,我们需要做的操作是:
找到a[i-1]的值出现的下一个位置y(如果不存在为-1时,就默认为n+1位置),执行add(i-1,-a[i-1]) 和 add(y,a[i-1])即可。
我们用next[i]=j来表示与a[i]值相同的下一个值出现在j位置。用hash[h]=i表示值为h的a[i]第一次出现的位置是i。
答案可能超过int 需要用long long
AC代码:1640ms一次AC
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int MAXN=50000+1000;
const int MAXV=1000000+100;
const int MAXM=200000+1000;
struct HASHMAP
{
int head[MAXV];//head[x]=i表示第一个出现的x值下标是i
int next[MAXN];
int size;
void init()
{
memset(head,-1,sizeof(head));
}
void insert(int i,int v)//将a[i]=v插入到HASHMAP中
{
next[i]=-1;
if(head[v]==-1)
head[v]=i;
else
{
int j=head[v];
while(next[j]!=-1)
j=next[j];
next[j]=i;
}
}
int find(int i)//找到a[i]值下一次出现的位置
{
return next[i];
}
}hm;
long long c[MAXN];
int a[MAXN];//初始读入的值
struct node
{
int l,r;
int index;
bool operator <(const node&b)const
{
return l<b.l;
}
}nodes[MAXM];//查询命令
long long ans[MAXM];//最终答案
int lowbit(int x)
{
return x&(-x);
}
long long sum(int x)
{
long long res=0;
while(x)
{
res +=c[x];
x-=lowbit(x);
}
return res;
}
void add(int x,int v)
{
while(x<MAXN)
{
c[x]+=v;
x+=lowbit(x);
}
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
int n,m;
scanf("%d",&n);
hm.init();
memset(c,0,sizeof(c));
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
if(hm.head[a[i]]==-1)//目前不存在a[i]值
add(i,a[i]);
hm.insert(i,a[i]);
}
scanf("%d",&m);
for(int i=1;i<=m;i++)
{
scanf("%d%d",&nodes[i].l,&nodes[i].r);
nodes[i].index=i;
}
sort(nodes+1,nodes+m+1);
int j=1;//表示当前处理第j个排序后的区间
for(int i=1;i<=n;i++)//从区间[i,R]一一处理
{
while(nodes[j].l==i)
{
ans[nodes[j].index]=sum(nodes[j].r);
j++;
}
if(j>m)
break;
add(i,-a[i]);
int next_ai=hm.find(i);
if(next_ai!=-1)
add(next_ai,a[i]);
}
for(int i=1;i<=m;i++)
printf("%I64d\n",ans[i]);
}
return 0;
}