在线做法:(主席树)
参考自:https://blog.csdn.net/aozil_yang/article/details/65448883
对于每一颗线段树我们只保留到目前为止每个数字最后出现的位置,即对于第k棵线段树我们将区间[1,K]中每个数字最后出现的位置处标记为1,之前的为0。
即若到目前为止序列从1到n出现的数字为:1,1,2,3,2,4的话,第六颗线段树是在这样的一个序列上维护区间和的:
0,1,0,1,1,1。
维护数字最后一次出现的位置,如果要将这个数字加入线段树时,之前已经有过这个数字,那么就先在上一棵线段树的基础上将这个数字之前最后一次出现的位置减1,然后在被修改了的线段树的基础上新的位置加1。
看代码注释吧;
/*
a[]为原始序列数组;
belong[]为该数字最后一次出现的位置,若之前没有这个数字则为0(我是从下标为1输入数组的)
root[]表示第i棵线段树的根节点;
updata(int last,int id,int val,int l,int r) 在last线段树的基础上将id处的位置加上val,[l,r]表示线段树维护的序列长度;
*/
for(int i=1;i<=n;++i){
int id=getid(a[i]);//离散化,也可以不离散化;
if(belong[id]){
int t=updata(root[i-1],belong[id],-1,1,n);//先在上一棵的基础上修改;
root[i]=updata(t,i,1,1,n);//再在新的基础上添加数;
}
else{
root[i]=updata(root[i-1],i,1,1,n);
}
belong[id]=i;
}
那么我们想要查询区间[L,R]内有多少数字该怎么查;
我们线段树维护的是到目前为止,序列中数字最后出现的位置,也就是说维护的是在一个长度为n的0,1序列上的东西,故查询相当于单点查找,若要查左子树,说明之后的数字都是满足条件的,直接将右子树的和加到答案里,若要查右子树,说明左子树的都不是满足条件的答案。
比如1,1,2,3,2,4 刚刚分析出到第六颗线段树为止其维护的序列是:
id: 1,2,3,4,5,6
val:0,1,0,1,1,1
那么我们如果要查区间[3,6]的话,是不是在第六颗线段树中找id大于等于3的位置的区间和吧,显然答案为3。
求解第k大问题的时候,我们是将序列离散化后得到长度为m的序列,进行权值的加减
而对于本次的问题,我们是维护的位置的权值,这一次直接在长度为n的序列上维护一个0,1序列。
#include
using namespace std;
typedef long long ll;
const int maxn=3e4+7;
int root[maxn],tot;
int m;
int a[maxn],b[maxn];
void quchong(int n){
sort(b+1,b+1+n);
m=unique(b+1,b+1+n)-b-1;
}
int getid(int x){
return lower_bound(b+1,b+1+m,x)-b;
}
struct Tree{
int lc,rc;
int sum;
}tree[maxn*20];
void pushup(int k){
tree[k].sum=tree[tree[k].lc].sum+tree[tree[k].rc].sum;
}
int build(int l,int r){
int k=++tot;
tree[k].sum=0;
if(l==r) return k;
int mid=(l+r)>>1;
tree[k].lc=build(l,mid);
tree[k].rc=build(mid+1,r);
return k;
}
int updata(int p,int id,int v,int l,int r){
int k=++tot;
tree[k]=tree[p];
if(l==r){
tree[k].sum+=v;
return k;
}
int mid=(l+r)>>1;
if(id<=mid) tree[k].lc=updata(tree[p].lc,id,v,l,mid);
else tree[k].rc=updata(tree[p].rc,id,v,mid+1,r);
pushup(k);
return k;
}
/*
相当于在第R棵线段树上找满足条件的数字和,
如果要递归到左子树的话,那么右子树代表[L,R] 必定都满足答案;
如果要递归到右子树的话,左子树代表的数字其位置均小于L故不可加左子树;
*/
int myfind(int p,int id,int l,int r){
if(l==r) return tree[p].sum;
int mid=(l+r)>>1;
if(id<=mid) return tree[tree[p].rc].sum+myfind(tree[p].lc,id,l,mid);
return myfind(tree[p].rc,id,mid+1,r);
}
int belong[maxn];
int main(){
int n;
scanf("%d",&n);
for(int i=1;i<=n;++i){
scanf("%d",&a[i]);
b[i]=a[i];
}
int l,r;
quchong(n);
int q;
root[0]=build(1,n);
for(int i=1;i<=n;++i){
int id=getid(a[i]);
if(belong[id]){
int t=updata(root[i-1],belong[id],-1,1,n);
root[i]=updata(t,i,1,1,n);
}
else{
root[i]=updata(root[i-1],i,1,1,n);
}
belong[id]=i;
}
scanf("%d",&q);
while(q--){
scanf("%d%d",&l,&r);
printf("%d\n",myfind(root[r],l,1,n));
}
return 0;
}
离线做法:(树状数组)
思路一样,也是维护数字最后出现的位置
只不过这一次我们先把所有的查询读入,然后按照查询的右端点排序,然后进行处理:
给一道例题吧,只不过这个题对一颗有根数查询以u为根节点的子树内不同颜色的个数
那么先dfs序将树转换为区间,那么就可以操作了:
DongDong数颜色
#include
using namespace std;
const int maxn=2e5+7;
int L[maxn],R[maxn];
int cc[maxn];
int w[maxn];
int innnnn,top,head[maxn];
struct Edge{
int v,next;
}edge[maxn];
void init(){
top=0;
memset(head,-1,sizeof(head));
}
void add(int u,int v){
edge[top].v=v;
edge[top].next=head[u];
head[u]=top++;
}
void dfs(int u,int fa){
L[u]=++innnnn;
cc[innnnn]=w[u];
int v;
for(int i=head[u];i!=-1;i=edge[i].next){
v=edge[i].v;
if(v==fa) continue;
dfs(v,u);
}
R[u]=++innnnn;
cc[innnnn]=w[u];
}
int sum[maxn];
int lowbit(int x){
return x&(-x);
}
int n;
void updata(int x,int v){
for(;x<=n;x+=lowbit(x)) sum[x]+=v;
}
int ask(int x){
int res=0;
for(;x;x-=lowbit(x)) res+=sum[x];
return res;
}
struct ASK{
int l,r;
int id;
bool operator <(const ASK& x)const{
return r