Task
1-n个人,等级从高到低,属于1-m地,除了1外每个人都有个直接导师,等级都比它高。q个询问,A,B(地区),输出有多少关系x∈A,y∈B,x是y的导师。
x是y的导师:当且仅当x是y的直接导师或是x是y的直接导师的导师。
N<=2e5,m<=25000,q<=2e5
Solution
分块决策
树:如果把i和i的直接导师连边,就构成一棵树。因为除了1外每个i都有一个父亲节点,而且编号都比自己小。那么,x是y的导师等价于y在x的子树里。
问题转化成:q个询问,输出有多少关系(ai,bi)表示bi在ai的子树里,且ai∈A,bi∈B.
子树的信息借助dfs序判断。用vector存下每个Xi地的人,以dfs序顺序从小到大排序。
方法一:O(alogb)
枚举A中的每个点i ,那么i的子树在[L[i],R[i]],二分查找在B这个区间内的点的个数。
一个有序数组在[l,r]之间的个数为upper_bound(r)-lower_bound(l)
方法二:O(bloga)
枚举B中的每个点i,查找有多个个Aj是它的祖先。假如Aj是Bi的祖先,那么Aj所代表的区间一定包括Bi。问题转化为Bi这个点被多少个区间覆盖,是常见的刷漆问题,用刷漆法求解。点k被刷了多少次,就是被多少个区间覆盖了。
如果求出每一个点的前缀和,对于k点被刷的次数可以用add[k],O(1)访问得到。但是数组存不下。刷漆法是左端点+1,右端点+1位置-1。因此前缀和只可能在任意一个区间的左右端点改变,即最多只有2*sz个点前缀和是不同的,任意两个不同的之间都是相同的,和前面一个一样。
如果我们只存下前缀和改变的点的位置和大小。对于一个Bi,找到第一个小于等于它的点j,j的前缀和等价于Bi位置的前缀和。
方法三:O(a+b)
因为A,B都是有序数组,要找到每个Bi前的第一个A,可以归并两个序列查找。
适用于a,b都很大的情况。
每一个询问,对应的三个方法的复杂度都是确定的,贪心选一个最小复杂度。
若多个询问相同,可以用map记录,也可以离线排序询问,避免重复计算。
计算复杂度带有log,可以调用系统函数,换底公式。但最好还是自己lg数组记录。
const int N=25003,M=2e5+3;
int n,m,q,ecnt,tot;
int head[M],L[M],R[M],lg[M],A[M],ans[M];
vector<int>s[N],id[N],val[N];
/* 所有下标都是在dfs序中位置 */
struct edge{
int t,nxt;
}e[M];
struct node1{
int id,a;
bool operator<(const node1 &t)const{
return id<t.id;
}
}add[M<<1];//查分前缀和
struct node2{
int a,b,id;
bool operator < (const node2 &t)const{
if(a!=t.a)return a<t.a;
return b<t.b;
}
bool operator == (const node2 &t)const{
return a==t.a&&b==t.b;
}
}Q[M];//离线询问
inline void addedge(int f,int t){
e[++ecnt]=(edge){t,head[f]};
head[f]=ecnt;
}
void input(){
int i,j,k,f,bel;
rd(n);rd(m);rd(q);
rep(i,1,n){
if(i>1){
rd(f);
addedge(f,i);
}
rd(bel);
s[bel].pb(i);//原编号
}
rep(i,1,q){
rd(Q[i].a);rd(Q[i].b);Q[i].id=i;
}
sort(Q+1,Q+1+q);
}
void dfs(int x){
L[x]=++tot;
A[tot]=x;
for(int i=head[x];i;i=e[i].nxt)dfs(e[i].t);
R[x]=tot;
}
void init(){
int i,j=0,k,x,l,r,sum;
lg[0]=-1;
rep(i,1,n){
if(!(i&(i-1)))lg[i]=++j;
else lg[i]=j;
}
dfs(1);
rep(x,1,m){//差分前缀和
tot=0;
for(i=0;i<s[x].size();++i){
l=L[s[x][i]]+1,r=R[s[x][i]];
if(l<=r){//不包括自己
add[++tot]=(node1){l,1};
add[++tot]=(node1){r+1,-1};
}
}
sort(add+1,add+1+tot);
sum=0;
for(i=1;i<=tot;i=j){
j=i;
while(j<=tot&&add[j].id==add[i].id)sum+=add[j].a,j++;
id[x].pb(add[i].id);
val[x].pb(sum);
}
}
rep(x,1,m){//原下标转化成dfs序位置 要有序!!!
for(i=0;i<s[x].size();++i)
s[x][i]=L[s[x][i]];
sort(s[x].begin(),s[x].end());
}
}
inline int Choose(int a,int b){
int x,y,z;
x=a*lg[b];
y=b*lg[a];
z=a+b;
if(x<=y&&x<=z)return 1;
if(y<=x&&y<=z)return 2;
return 3;
}
inline int work1(int a,int b){//O(alogb)
int i,j,ans=0;
for(i=0;i<s[a].size();++i)
ans+=upper_bound(s[b].begin(),s[b].end(),R[A[s[a][i]]])-lower_bound(s[b].begin(),s[b].end(),L[A[s[a][i]]]);
return ans;
}
inline int work2(int a,int b){//O(bloga)
int i,j,k,ans=0;
for(i=0;i<s[b].size();++i){
k=upper_bound(id[a].begin(),id[a].end(),s[b][i])-id[a].begin()-1;
if(k>=0)ans+=val[a][k];
}return ans;
}
inline int work3(int a,int b){//O(a+b)
int i,j,k,l=0,ans=0,res=0;
for(i=0;i<s[b].size();++i){
while(l<id[a].size()&&id[a][l]<=s[b][i]){res=val[a][l];++l;}
ans+=res;
}
return ans;
}
inline void solve(){
int i,j,k,c;
Q[0]=(node2){0,0,0};
rep(i,1,q){
if(Q[i]==Q[i-1]){ans[Q[i].id]=ans[Q[i-1].id];continue;}//避免重复
c=Choose(s[Q[i].a].size(),s[Q[i].b].size());
if(c==1)ans[Q[i].id]=work1(Q[i].a,Q[i].b);
else if(c==2)ans[Q[i].id]=work2(Q[i].a,Q[i].b);
else ans[Q[i].id]=work3(Q[i].a,Q[i].b);
}
rep(i,1,q)sc(ans[i]);
}
int main(){
int i;
input();
init();
solve();
return 0;
}