[NOI2015]软件包管理器(巧用线段树)
题目
解说
线段树基本板子的可塑性其实非常强悍,针对不同的题目要求只要稍作修改就可以发挥不同的作用。这道题让我更深刻地理解了这一点。
本题和普通的树链剖分+线段树最大的区别在于它的每个结点并不储存一个数值,而是只有两种状态:安装与未安装。针对这一特点,我们对线段树的板子做以下修改即可:
-
lazy标记:由于现在每个节点只有两种状态,\(lazy\)标记也只有两种数值就够了,比如\(1\)代表该区间所有软件全部被安装,\(-1\)代表该区间所有软件全部没有被安装。
-
pushdown操作:之前\(pushdown\)仅仅把父节点的\(lazy\)标记下放即可,但现在\(lazy\)标记有两种,所以下放之前应当先判断:\(lazy\)标记为\(1\)则代表全部安装,那么就应当把子节点的安装数量变为区间内总节点数;\(lazy\)标记为\(1\)则代表全部卸载,那么就应当把子节点的安装数量变为\(0\)。
-
update操作:之前传参时传递的是向该区间内加的数,现在则应更改为操作模式,即卸载还是安装?安装则把\(lazy\)标记记为\(1\),把子节点的安装数量变为区间内总节点数;卸载则把\(lazy\)标记记为\(-1\),把子节点的安装数量变为\(0\)。
-
其余操作均保持不变
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=100000+3;
int head[maxn],tot,dep[maxn],size[maxn],son[maxn],top[maxn],n,q,fa[maxn],dfn[maxn],dfn_clock;
struct edge{
int to,next;
}e[maxn<<1];
struct node{
int l,r,size,w;//w表示该区间共安装了几个
int lazy;//lazy表示该区间是否全部安装或全部卸载
//1为全部安装,-1为全部卸载,0为无标记
}tree[maxn<<2];
void add(int a,int b){
e[++tot].next=head[a];
head[a]=tot;
e[tot].to=b;
}
void dfs(int u,int father){
fa[u]=father;
size[u]=1;
for(int i=head[u];i;i=e[i].next){
int v=e[i].to;
if(v==fa[u]) continue;
dep[v]=dep[u]+1;
dfs(v,u);
size[u]+=size[v];
if(!son[u]||size[v]>size[son[u]]) son[u]=v;
}
}
void get_top(int u,int t){
top[u]=t;
dfn[u]=++dfn_clock;
if(son[u]) get_top(son[u],t);
for(int i=head[u];i;i=e[i].next){
int v=e[i].to;
if(v==fa[u]||v==son[u]) continue;
get_top(v,v);
}
}
void build(int rt,int l,int r){
if(l>r) swap(l,r);
tree[rt].l=l,tree[rt].r=r,tree[rt].size=r-l+1;
if(l==r) return;
int mid=l+r>>1;
build(rt<<1,l,mid);
build(rt<<1|1,mid+1,r);
}
void renew(int rt){
tree[rt].w=tree[rt<<1].w+tree[rt<<1|1].w;
}
void push_down(int rt){//修改版push_down操作
if(tree[rt].lazy==1){
tree[rt<<1].w=tree[rt<<1].size;
tree[rt<<1|1].w=tree[rt<<1|1].size;
tree[rt<<1].lazy=1;
tree[rt<<1|1].lazy=1;
}
if(tree[rt].lazy==-1){
tree[rt<<1].w=0;
tree[rt<<1|1].w=0;
tree[rt<<1].lazy=-1;
tree[rt<<1|1].lazy=-1;
}
tree[rt].lazy=0;
}
void update(int rt,int s,int t,int mode){//mode即代表操作模式
if(s>t) swap(s,t);
if(s<=tree[rt].l&&t>=tree[rt].r){
tree[rt].lazy=mode;
if(mode==1) tree[rt].w=tree[rt].size;
if(mode==-1) tree[rt].w=0;
return;
}
push_down(rt);
int mid=tree[rt].l+tree[rt].r>>1;
if(s<=mid) update(rt<<1,s,t,mode);
if(t>mid) update(rt<<1|1,s,t,mode);
renew(rt);
}
int query(int rt,int s,int t){//查询[s,t]已安装的数量
if(s>t) swap(s,t);
int ans=0;
if(s<=tree[rt].l&&t>=tree[rt].r) return tree[rt].w;
push_down(rt);
int mid=tree[rt].l+tree[rt].r>>1;
if(s<=mid) ans+=query(rt<<1,s,t);
if(t>mid) ans+=query(rt<<1|1,s,t);
return ans;
}
int ask(int u){//树上查询u到根之间已安装数
int ans=0;
while(top[u]){
ans+=query(1,dfn[top[u]],dfn[u]);
u=fa[top[u]];
}
ans+=query(1,1,dfn[u]);
return ans;
}
void treeadd(int u,int mode){//树上全部安装或卸载u到根之间全部软件
while(top[u]){
update(1,dfn[top[u]],dfn[u],mode);
u=fa[top[u]];
}
update(1,1,dfn[u],mode);
}
int main(){
scanf("%d",&n);
for(int i=1;i<n;i++){
int tmp;
scanf("%d",&tmp);
add(i,tmp),add(tmp,i);
}
dfs(0,0);
get_top(0,0);
build(1,1,n);
scanf("%d",&q);
while(q--){
char s[10];int x;
scanf("%s%d",s,&x);
if(s[0]=='i'){
printf("%d\n",dep[x]+1-ask(x));
treeadd(x,1);
}
else{
printf("%d\n",query(1,dfn[x],dfn[x]+size[x]-1));
update(1,dfn[x],dfn[x]+size[x]-1,-1);
}
}
return 0;
}
幸甚至哉,歌以咏志。
签名:
我将轻轻叹息,叙述这一切,
许多许多年以后:
林子里有两条路,我——
选择了行人稀少的那一条,
它改变了我的一生。