法一:
线段树——
用一棵线段树+主席树
线段树就是拿来统计sum的 (sigma a)
主席树就是拿来统计个数为2的数的和del
主席树的具体操作:
a[i] i前有一个数=a[i] 则在第i棵树的第i个位置 +a[i]
a[i] i前有两个数=a[i] :
a[i]- 4 4 4
del- -4 +4 0 这样查询前三个时sigmadel=0 查询后两个时sigma=4
再比如
a[i] - 4 4 4 4
del 0 -4 +4 0
当查询[l,r]时 查询第r棵树的l,r区间
(这个做法详见谢大佬博客)
法二:
莫队——
没什么好说的看代码就行
考试时想到了莫队但是很执念的认为莫队就要分块预处理……说到底还是对莫队不熟。
以及最近很喜欢犯一些sb错误?
调了那么久,就是因为
else if(exi[aa[pr]]==1) rt+=aa[pr];
把pr写成了pl ……
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
#define ll long long
const int N=1e5+5;
int exi[N];
int n,m,aa[N],pos[N],block;
ll ans[N];
struct data{
int l,r,id;
}a[N];
int cmp(data a,data b)
{
if(pos[a.l]==pos[b.l]) return a.r<b.r;
return pos[a.l]<pos[b.l];
}
int main()
{
freopen("abnormal.in","r",stdin);
freopen("abnormal.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",&aa[i]);
block=sqrt(n);
for(int i=1;i<=n;i++)
pos[i]=(i-1)/block+1;
for(int i=1;i<=m;i++){
scanf("%d%d",&a[i].l,&a[i].r);
a[i].id=i;
}
sort(a+1,a+1+m,cmp);
int pl=a[1].l,pr=a[1].r;ll rt=0;
for(int i=a[1].l;i<=a[1].r;i++){
exi[aa[i]]++;
if(exi[aa[i]]==2) rt-=aa[i];
else if(exi[aa[i]]==3) rt+=3*aa[i];
else rt+=aa[i];
}
ans[a[1].id]=rt;
for(int i=2;i<=m;i++){
for(;pl<a[i].l;pl++){
exi[aa[pl]]--;
//if(exi[aa[pl]]==2) rt-=3*aa[pl];
if(exi[aa[pl]]==2||!exi[aa[pl]]) rt-=(exi[aa[pl]]+1)*aa[pl];
else if(exi[aa[pl]]==1) rt+=aa[pl];//2->1
else rt-=aa[pl];
}
for(;pl>a[i].l;pl--){
exi[aa[pl-1]]++;
if(exi[aa[pl-1]]==2) rt-=aa[pl-1];
else if(exi[aa[pl-1]]==3) rt+=3*aa[pl-1];
else rt+=aa[pl-1];
}
for(;pr<a[i].r;pr++){
exi[aa[pr+1]]++;
if(exi[aa[pr+1]]==2) rt-=aa[pr+1];
else if(exi[aa[pr+1]]==3) rt+=3*aa[pr+1];
else rt+=aa[pr+1];
}
for(;pr>a[i].r;pr--){
exi[aa[pr]]--;
if(exi[aa[pr]]==2) rt-=3*aa[pr];
else if(exi[aa[pr]]==1) rt+=aa[pr];//2->1!!!
else rt-=aa[pr];
}
ans[a[i].id]=rt;
}
for(int i=1;i<=m;i++)
printf("%I64d\n",ans[i]);
return 0;
}

本文介绍了解决特定区间统计问题的两种方法:线段树加主席树的方法及莫队算法。前者通过线段树统计区间内元素总和,并利用主席树统计特定条件的元素个数;后者采用离线查询的方式,通过调整窗口大小实现区间统计。
1006

被折叠的 条评论
为什么被折叠?



