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.

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

[NOI2015] 软件包管理器【树链剖分+线段树区间覆盖】的更多相关文章

  1. bzoj 4196 [Noi2015]软件包管理器 (树链剖分+线段树)

    4196: [Noi2015]软件包管理器 Time Limit: 10 Sec  Memory Limit: 512 MBSubmit: 2852  Solved: 1668[Submit][Sta ...

  2. 【BZOJ-2325】道馆之战 树链剖分 + 线段树

    2325: [ZJOI2011]道馆之战 Time Limit: 40 Sec  Memory Limit: 256 MBSubmit: 1153  Solved: 421[Submit][Statu ...

  3. 【BZOJ2243】[SDOI2011]染色 树链剖分+线段树

    [BZOJ2243][SDOI2011]染色 Description 给定一棵有n个节点的无根树和m个操作,操作有2类: 1.将节点a到节点b路径上所有点都染成颜色c: 2.询问节点a到节点b路径上的 ...

  4. BZOJ2243 (树链剖分+线段树)

    Problem 染色(BZOJ2243) 题目大意 给定一颗树,每个节点上有一种颜色. 要求支持两种操作: 操作1:将a->b上所有点染成一种颜色. 操作2:询问a->b上的颜色段数量. ...

  5. POJ3237 (树链剖分+线段树)

    Problem Tree (POJ3237) 题目大意 给定一颗树,有边权. 要求支持三种操作: 操作一:更改某条边的权值. 操作二:将某条路径上的边权取反. 操作三:询问某条路径上的最大权值. 解题 ...

  6. bzoj4034 (树链剖分+线段树)

    Problem T2 (bzoj4034 HAOI2015) 题目大意 给定一颗树,1为根节点,要求支持三种操作. 操作 1 :把某个节点 x 的点权增加 a . 操作 2 :把某个节点 x 为根的子 ...

  7. HDU4897 (树链剖分+线段树)

    Problem Little Devil I (HDU4897) 题目大意 给定一棵树,每条边的颜色为黑或白,起始时均为白. 支持3种操作: 操作1:将a->b的路径中的所有边的颜色翻转. 操作 ...

  8. Aizu 2450 Do use segment tree 树链剖分+线段树

    Do use segment tree Time Limit: 1 Sec Memory Limit: 256 MB 题目连接 http://www.bnuoj.com/v3/problem_show ...

  9. 【POJ3237】Tree(树链剖分+线段树)

    Description You are given a tree with N nodes. The tree’s nodes are numbered 1 through N and its edg ...

  10. HDU 2460 Network(双连通+树链剖分+线段树)

    HDU 2460 Network 题目链接 题意:给定一个无向图,问每次增加一条边,问个图中还剩多少桥 思路:先双连通缩点,然后形成一棵树,每次增加一条边,相当于询问这两点路径上有多少条边,这个用树链 ...

随机推荐

  1. Docker的镜像 导出导入

    查看当前已经安装的镜像 vagrant@vagrant:~$ sudo docker images REPOSITORY TAG IMAGE ID CREATED SIZE mysql 5.7.22 ...

  2. CSS——溢出文字隐藏

    溢出的文字隐藏 word-break:自动换行 normal 使用浏览器默认的换行规则. break-all 允许在单词内换行. keep-all 只能在半角空格或连字符处换行. 主要处理英文单词 w ...

  3. (转)Android中px与dip,sp与dip等的转换工具类

    功能 通常在代码中设置组件或文字大小只能用px,通过这个工具类我们可以把dip(dp)或sp为单位的值转换为以px为单位的值而保证大小不变.方法中的参数请参考http://www.cnblogs.co ...

  4. 机器学习 101 Mahout 简介 建立一个推荐引擎 使用 Mahout 实现集群 使用 Mahout 实现内容分类 结束语 下载资源

      机器学习 101 Mahout 简介 建立一个推荐引擎 使用 Mahout 实现集群 使用 Mahout 实现内容分类 结束语 下载资源 相关主题   在信息时代,公司和个人的成功越来越依赖于迅速 ...

  5. 为什么程序员都不喜欢使用switch,而是大量的 if……else if ?

    作者:熊爸爸 原文:http://3g.163.com/tech/article/E02RDE6C0511SDDL.html 请用5秒钟的时间查看下面的代码是否存在bug. OK,熟练的程序猿应该已经 ...

  6. HTML5伪类选择器表单验证

    input : required   选择必填表单域 input : focus : invalid 选择当前聚焦的且含有非法输入值的表单域 input : focus : valid 选择当前聚焦的 ...

  7. java读取字符串,生成txt文件

    /** * 读取字符串,生成txt 文件 已解决未设置编码时,在项目中直接打开文件,中文乱码问题 * WriteText.writeToText(musicInfo,fileName)直接调用 * * ...

  8. 使用CEfSharp之旅(6)拦截网络请求 截取response返回

    原文:使用CEfSharp之旅(6)拦截网络请求 截取response返回 版权声明:本文为博主原创文章,未经博主允许不得转载.可点击关注博主 ,不明白的进群191065815 我的群里问 https ...

  9. 编写Reduce处理逻辑

  10. php socket模拟http中post或get提交数据

    php socket模拟http中post或者get提交数据的示例代码. 代码: sock_post.php: <?php /** * php socket模拟post\get请求 * 编辑:脚 ...