[NOI2015] 软件包管理器【树链剖分+线段树区间覆盖】
Online Judge:Luogu-P2146
Label:树链剖分,线段树区间覆盖
题目大意
\(n\)个软件包(编号0~n-1),他们之间的依赖关系用一棵含\(n-1\)条边的树来描述。一共两种操作:
install x:表示安装软件包xuninstall 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] 软件包管理器【树链剖分+线段树区间覆盖】的更多相关文章
- bzoj 4196 [Noi2015]软件包管理器 (树链剖分+线段树)
4196: [Noi2015]软件包管理器 Time Limit: 10 Sec Memory Limit: 512 MBSubmit: 2852 Solved: 1668[Submit][Sta ...
- 【BZOJ-2325】道馆之战 树链剖分 + 线段树
2325: [ZJOI2011]道馆之战 Time Limit: 40 Sec Memory Limit: 256 MBSubmit: 1153 Solved: 421[Submit][Statu ...
- 【BZOJ2243】[SDOI2011]染色 树链剖分+线段树
[BZOJ2243][SDOI2011]染色 Description 给定一棵有n个节点的无根树和m个操作,操作有2类: 1.将节点a到节点b路径上所有点都染成颜色c: 2.询问节点a到节点b路径上的 ...
- BZOJ2243 (树链剖分+线段树)
Problem 染色(BZOJ2243) 题目大意 给定一颗树,每个节点上有一种颜色. 要求支持两种操作: 操作1:将a->b上所有点染成一种颜色. 操作2:询问a->b上的颜色段数量. ...
- POJ3237 (树链剖分+线段树)
Problem Tree (POJ3237) 题目大意 给定一颗树,有边权. 要求支持三种操作: 操作一:更改某条边的权值. 操作二:将某条路径上的边权取反. 操作三:询问某条路径上的最大权值. 解题 ...
- bzoj4034 (树链剖分+线段树)
Problem T2 (bzoj4034 HAOI2015) 题目大意 给定一颗树,1为根节点,要求支持三种操作. 操作 1 :把某个节点 x 的点权增加 a . 操作 2 :把某个节点 x 为根的子 ...
- HDU4897 (树链剖分+线段树)
Problem Little Devil I (HDU4897) 题目大意 给定一棵树,每条边的颜色为黑或白,起始时均为白. 支持3种操作: 操作1:将a->b的路径中的所有边的颜色翻转. 操作 ...
- 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 ...
- 【POJ3237】Tree(树链剖分+线段树)
Description You are given a tree with N nodes. The tree’s nodes are numbered 1 through N and its edg ...
- HDU 2460 Network(双连通+树链剖分+线段树)
HDU 2460 Network 题目链接 题意:给定一个无向图,问每次增加一条边,问个图中还剩多少桥 思路:先双连通缩点,然后形成一棵树,每次增加一条边,相当于询问这两点路径上有多少条边,这个用树链 ...
随机推荐
- animation,transition,transform小练习
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- CSIC_716_20191108【文件的操作,以及彻底解决编码问题的方案】
关于编码的问题: 在平时编写代码,涉及到打开文件时,常常遇到字符编码的报错, 通过总结,得出以下规律 如果在操作过程中涉及到调用文本文档,一定要在文本文档开头申明编码方式(# coding:XXXX ...
- php多维数组排序方案。按照姓名 首字符 等排序
//定义一个学生数组 $students = array( 256=>array('name'=>'jon','grade'=>98.5), 2=>arra ...
- duilib教程之duilib入门简明教程14.部分bug 2
上一个教程中提到了ActiveX的Bug,即如果主窗口直接用变量生成,则关闭窗口时会产生崩溃 如果用new的方式生成,则不会崩溃,所以给出一个临时的快速解决方案,即主窗口都用new生成,_t ...
- VS2010-MFC(MFC常用类:MFC异常处理)
转自:http://www.jizhuomi.com/software/236.html 上一节讲了CFile文件操作类,本节主要来说说MFC异常处理. 在鸡啄米C++编程入门系列的最后一节鸡啄米:C ...
- python笔记三
# 数据读写不一定是文件,也可以在内存中读写 # StringIO就是在内存中读写str from io import StringIO f = StringIO() # 要把str写入StringI ...
- spring源码读书笔记
如果我们在web项目里面使用spring的话,通常会在web.xml里面配置一个listener. <listener> <listener-class> org.spring ...
- Caused by: java.lang.NoSuchMethodError: org.hibernate.engine.spi.SessionFactoryImplementor.getProperties()Ljava/util/Properties;
报错信息 Error starting ApplicationContext. To display the auto-configuration report re-run your applica ...
- LeetCode 8.字符串转换整数 (atoi)(Python3)
题目: 请你来实现一个 atoi 函数,使其能将字符串转换成整数. 首先,该函数会根据需要丢弃无用的开头空格字符,直到寻找到第一个非空格的字符为止. 当我们寻找到的第一个非空字符为正或者负号时,则将该 ...
- LINUX挂接移动硬盘
对linux系统而言,USB接口的移动硬盘是当作SCSI设备对待的.插入移动硬盘之前,应先用fdisk –l 或 more /proc/partitions查看系统的硬盘和硬盘分区情况. [root ...