题目
一种可行并且非常优秀常数小一大截的做法是BIT并且非常显然。
所以我们以这道题为例题讲一下线段树合并。
线段树合并要做的就是把两棵线段树对应位置的值全部加起来得到一棵新的线段树。
以下是一种可行的方法:
我们从根节点开始往下做,如果要合并的两棵线段树中有一棵线段树不存在当前节点,那么我们可以直接返回另一棵线段树的当前节点,因为当前节点及其子树内的信息都由这棵线段树决定。
否则我们合并两棵线段树对应位置的信息,然后递归左右儿子。
假设我们总共插入了\(n\)条信息,那么线段树的总结点个数是\(n\log n\)级别的,时间复杂度也为\(O(n\log n)\)。
然后回到这一题,我们可以把权值离散化,每个节点开一个动态开点权值线段树,然后自底向上线段树合并。
因为总共插入的信息是\(n\),所以复杂度为\(O(n\log n)\)。
但是常数贼大,跑得比BIT慢到不知道哪里去了。
#include<bits/stdc++.h> #define pb push_back #define mid ((l+r)>>1) using namespace std; namespace IO { char ibuf[(1<<21)+1],obuf[(1<<21)+1],st[15],*iS,*iT,*oS=obuf,*oT=obuf+(1<<21); char Get(){return (iS==iT? (iT=(iS=ibuf)+fread(ibuf,1,(1<<21)+1,stdin),(iS==iT? EOF:*iS++)):*iS++);} void Flush(){fwrite(obuf,1,oS-obuf,stdout),oS=obuf;} void Put(char x){*oS++=x;if(oS==oT)Flush();} int read(){int x=0,c=Get();while(!isdigit(c))c=Get();while(isdigit(c))x=x*10+c-48,c=Get();return x;} void write(int x){int top=0;if(!x)Put('0');while(x)st[++top]=(x%10)+48,x/=10;while(top)Put(st[top--]);Put('\n');} } using namespace IO; const int N=100007; int a[N],n,h[N],m,fa[N],ans[N],root[N],ls[N<<5],rs[N<<5],sum[N<<5],cnt;vector<int>E[N]; void update(int &p,int l,int r,int x) { if(!p) p=++cnt;++sum[p]; if(l==r) return ; x<=mid? update(ls[p],l,mid,x):update(rs[p],mid+1,r,x); } int query(int p,int l,int r,int ql) { if(!p) return 0; if(ql<=l) return sum[p]; return query(rs[p],mid+1,r,ql)+(ql<=mid? query(ls[p],l,mid,ql):0); } int merge(int u,int v) { if(!u||!v) return u+v; int p=++cnt; sum[cnt]=sum[u]+sum[v],ls[p]=merge(ls[u],ls[v]),rs[p]=merge(rs[u],rs[v]); return p; } void dfs(int u) { for(int v:E[u]) if(v^fa[u]) dfs(v),root[u]=merge(root[u],root[v]); ans[u]=query(root[u],1,m,a[u]+1),update(root[u],1,m,a[u]); } int main() { n=read(); for(int i=1;i<=n;++i) h[i]=a[i]=read(); sort(h+1,h+n+1),m=unique(h+1,h+n+1)-h-1; for(int i=1;i<=n;++i) a[i]=lower_bound(h+1,h+m+1,a[i])-h; for(int i=2;i<=n;++i) E[fa[i]=read()].pb(i); dfs(1); for(int i=1;i<=n;++i) write(ans[i]); return Flush(),0; }