3086 区域发展

3 篇文章 0 订阅
2 篇文章 0 订阅

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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
资源包主要包含以下内容: ASP项目源码:每个资源包中都包含完整的ASP项目源码,这些源码采用了经典的ASP技术开发,结构清晰、注释详细,帮助用户轻松理解整个项目的逻辑和实现方式。通过这些源码,用户可以学习到ASP的基本语法、服务器端脚本编写方法、数据库操作、用户权限管理等关键技术。 数据库设计文件:为了方便用户更好地理解系统的后台逻辑,每个项目中都附带了完整的数据库设计文件。这些文件通常包括数据库结构图、数据表设计文档,以及示例数据SQL脚本。用户可以通过这些文件快速搭建项目所需的数据库环境,并了解各个数据表之间的关系和作用。 详细的开发文档:每个资源包都附有详细的开发文档,文档内容包括项目背景介绍、功能模块说明、系统流程图、用户界面设计以及关键代码解析等。这些文档为用户提供了深入的学习材料,使得即便是从零开始的开发者也能逐步掌握项目开发的全过程。 项目演示与使用指南:为帮助用户更好地理解和使用这些ASP项目,每个资源包中都包含项目的演示文件和使用指南。演示文件通常以视频或图文形式展示项目的主要功能和操作流程,使用指南则详细说明了如何配置开发环境、部署项目以及常见问题的解决方法。 毕业设计参考:对于正在准备毕业设计的学生来说,这些资源包是绝佳的参考材料。每个项目不仅功能完善、结构清晰,还符合常见的毕业设计要求和标准。通过这些项目,学生可以学习到如何从零开始构建一个完整的Web系统,并积累丰富的项目经验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值