ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

[NOI2015] 软件包管理器【树链剖分+线段树区间覆盖】

2019-09-05 19:03:00  阅读:264  来源: 互联网

标签:管理器 剖分 int top 树链 软件包 区间 操作 son


Online JudgeLuogu-P2146

Label:树链剖分,线段树区间覆盖

题目大意

\(n\)个软件包(编号0~n-1),他们之间的依赖关系用一棵含\(n-1\)条边的树来描述。一共两种操作:

  • install x:表示安装软件包x
  • uninstall x:表示卸载软件包x

安装\(x\)时,必须得先安装x到根节点路径上所有的软件包;而卸载\(x\)时,必须得先卸载x子树中所有已经安装的软件包。对于每个操作,输出该操作前后,安装状态产生变化的节点的数量

对于100%的数据,\(n,q<=100000\)

输入

输入文件的第1行包含1个整数n,表示软件包的总数。软件包从0开始编号。

随后一行包含n−1个整数,相邻整数之间用单个空格隔开,分别表示1,2,3,⋯,n−2,n−1号软件包依赖的软件包的编号。

接下来一行包含1个整数q,表示询问的总数。之后q行,每行1个询问。询问分为两种:

install x:表示安装软件包x

uninstall x:表示卸载软件包x

你需要维护每个软件包的安装状态,一开始所有的软件包都处于未安装状态。

对于每个操作,你需要输出这步操作会改变多少个软件包的安装状态,随后应用这个操作(即改变你维护的安装状态)。

输出

输出文件包括q行。

输出文件的第i行输出1个整数,为第i步操作中改变安装状态的软件包数。

样例

Input#1

7
0 0 0 1 1 5
5
install 5
install 6
uninstall 1
install 4
uninstall 0

Output#1

3
1
3
2
3

题解

将这两种操作对应到树上。

1.安装时:

将x到根节点的路径上所有点的安装状态赋值为已安装:1

2.卸载时:

将x子树内的点的安装状态赋值为未安装:0

发现两个操作都需要对一段连续区间进行赋值,注意不是修改,不过都可以利用线段树完成,至于线段树上区间的赋值和修改的具体差别在文末具体bb阐述。

第一个操作可以利用树链剖分跳重链,在O\((log^2N)\)的时间内完成跳跃以及修改一条链上的点的状态。

第二个操作,直接根据dfs序,对子树对应的区间进行区间赋值即可,单次操作的复杂度是\(O(logN)\)。

综上整个算法的时间复杂度为\(O(q \cdot log^2N)\)。

完整代码如下:

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
inline int read(){
    int x=0;char c=getchar();
    while(c<'0'||c>'9')c=getchar();
    while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
    return x;
}
struct edge{
    int to,nxt;
}e[N];
int head[N],cnt;
inline void link(int u,int v){e[++cnt]=(edge){v,head[u]};head[u]=cnt;}


struct node{
    int l,r,w,lazy;
}b[N<<2];
void build(int o,int l,int r){
    b[o].l=l,b[o].r=r,b[o].lazy=-1;
    if(l==r)return;
    int mid=l+r>>1;
    build(o<<1,l,mid),build(o<<1|1,mid+1,r);
}
void down(int o){
    int g=b[o].lazy;
    if(g==-1)return;
    b[o<<1].w=(b[o<<1].r-b[o<<1].l+1)*g;
    b[o<<1|1].w=(b[o<<1|1].r-b[o<<1|1].l+1)*g;
    b[o<<1].lazy=b[o<<1|1].lazy=g;
    b[o].lazy=-1;
}
void update(int o,int l,int r,int d){
    if(b[o].l==l&&b[o].r==r){
        b[o].w=(b[o].r-b[o].l+1)*d;
        b[o].lazy=d;
        return;
    }
    down(o);
    int mid=b[o].l+b[o].r>>1;
    if(r<=mid)update(o<<1,l,r,d);
    else if(l>=mid+1)update(o<<1|1,l,r,d);
    else{
        update(o<<1,l,mid,d);update(o<<1|1,mid+1,r,d);
    }
    b[o].w=b[o<<1].w+b[o<<1|1].w;
}


int n,q;
int sz[N],dep[N],fa[N],top[N],son[N];
int li[N],ri[N],tot;
void predfs(int x,int f){
    sz[x]=1;fa[x]=f;dep[x]=dep[f]+1;
    for(int i=head[x];i;i=e[i].nxt){
        int y=e[i].to;
        predfs(y,x);
        sz[x]+=sz[y];
        if(!son[x]||sz[y]>sz[son[x]])son[x]=y;
    }
}
void redfs(int x,int tp){
    top[x]=tp;li[x]=++tot;
    if(son[x])redfs(son[x],tp);
    for(int i=head[x];i;i=e[i].nxt){
        int y=e[i].to;
        if(y!=son[x])redfs(y,y);
    }
    ri[x]=tot;
}
void boom(int x,int y,int d){
    while(top[x]!=top[y]){      
        if(dep[top[x]]<dep[top[y]])swap(x,y);
        update(1,li[top[x]],li[x],d);
        x=fa[top[x]];
    }
    if(dep[x]>dep[y])swap(x,y);
    update(1,li[x],li[y],d);
}
int main(){
    n=read();
    for(int i=2,u;i<=n;i++){
        u=read();u++;
        link(u,i);
    }
    predfs(1,0);redfs(1,1);
    build(1,1,n);
    q=read();
    while(q--){
        char op[13];scanf("%s",op);
        int x=read(),base=b[1].w;
        x++;    
        if(op[0]=='i'){         
            boom(1,x,1);
            printf("%d\n",b[1].w-base);
        }
        else{   
            update(1,li[x],ri[x],0);
            printf("%d\n",base-b[1].w); 
        }
    }
}

End

借这道题整理一下几个常见问题及模板:

一、树链剖分

part1

树剖五件套:\(sz[],son[],fa[],dep[],top[]\)(分别对应子树大小,重儿子编号,父亲节点编号,深度,所属重链的顶端节点编号)

注意每个点必落在一条重链上,当只有一个点时,链退化成点,那个唯一的点,就是该条重链的top

part2

实现上主要分三个部分

predfs(root);//主要处理sz,son,fa,dep这四个数组
redfs(root,root);//主要处理top,题目需要时处理li[],ri[](dfs序)
calc(u,v);//统计路径(u,v)上的值、或者是对路径(u,v)进行修改

这里有一个细节,处理dfs序的时候必须得在\(redfs\)时进行,因为我们得保证一条重链上的\(li[]\)是连续的,这样才能映射到线段树上,对连续的一段链进行区间修改。当然,这样处理dfs序仍然能表示一个节点的子树->\([li[x],ri[x]]\)。

下面给出三个部分的模板代码

predfs

void predfs(int x,int f){
    sz[x]=1;fa[x]=f;dep[x]=dep[f]+1;
    for(int i=head[x];i;i=e[i].nxt){
        int y=e[i].to;
        predfs(y,x);
        sz[x]+=sz[y];
        if(!son[x]||sz[y]>sz[son[x]])son[x]=y;
    }
}

redfs

void redfs(int x,int tp){
    top[x]=tp;li[x]=++tot;
    if(son[x])redfs(son[x],tp);
    for(int i=head[x];i;i=e[i].nxt){
        int y=e[i].to;if(y!=son[x])redfs(y,y);
    }
    ri[x]=tot;
}

calc

不同的题目,主要的变化就在第三部分,下面是几个常见问题的板子

1.求LCA

int LCA(int x,int y){
    while(top[x]!=top[y]){
        if(dep[top[x]]<dep[top[y]])swap(x,y);
        x=fa[top[x]];
    }
    return dep[x]<dep[y]?x:y;
}

2.查询、修改一段路径

void boom(int x,int y,int d){
    while(top[x]!=top[y]){      
        if(dep[top[x]]<dep[top[y]])swap(x,y);
        update(1,li[top[x]],li[x],d);//修改时:ans+=query();
        x=fa[top[x]];
    }
    if(dep[x]>dep[y])swap(x,y);
    update(1,li[x],li[y],d);//修改时:ans+=query();
}

part3

安利一篇树剖的blog

二、树状数组、线段树常见操作

Ⅰ.树状数组

相关blog:树状数组(含进阶版)

普通的树状数组支持:

0.单点修改+单点查询(普通数组就可以了2333)

1.单点更新+区间求和

2.区间更新+单点求值(利用差分)

3.单点更新+求前缀的最值(必须满足修改的值呈现单调性


Ⅱ.线段树

相关操作比较多就不一一阐述了。这里区分一下本题覆盖修改(可理解为增加某个值)的操作。

part1:懒标记、节点的不同意义

覆盖操作和修改操作都需要打一个懒标记,但意义和用法有所不同。

对于更为常见的修改操作,\(lazy\)用来给儿子下传需要增加的累计值。节点\(o\)表示\([b[o].l,b[o].r]\)这整个区间的整合信息;

而对于覆盖操作,\(lazy\)用来标记该给这个区间赋的值,它与之前这棵树长什么样没有关系。对于非叶子节点的节点\(o\)来说它本身没有具体含义。

part2:具体实现

我们知道,在普通的区间修改操作时:

build:一开始建树时\(lazy\)就赋值为0,表示需要下传给儿子的累计值为0;

update:对于区间\([ql,qr]\)的修改操作,将被包含的节点的值\(w\)增加某个值,懒标记\(lazy\)增加某个值(具体操作视问题而定);

down:对于某节点\(o\)的下传操作,将其左右儿子的\(w\)增加某个值,懒标记\(lazy\)增加某个值。

而对于区间覆盖来说,如果一开始建树时不能直接赋值为0(假如有某个赋值为0的操作,那就重了),我们可以将其赋值为一个接下来操作中不会覆盖到的值,举个例子:假如你只对区间覆盖一个自然数,那么你就可以将\(lazy\)初始赋值为-1。而接下来对应操作中也不应该是增加,而是赋值

part3:同时包含区间覆盖+区间修改的问题

可以参考这篇blog.

其实本质不变,开两个标记,根据情况分类讨论一下即可。

标签:管理器,剖分,int,top,树链,软件包,区间,操作,son
来源: https://www.cnblogs.com/Tieechal/p/11468990.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有