[NOI2015]软件包管理器(巧用线段树)

题目

原题链接

解说

线段树基本板子的可塑性其实非常强悍,针对不同的题目要求只要稍作修改就可以发挥不同的作用。这道题让我更深刻地理解了这一点。

本题和普通的树链剖分+线段树最大的区别在于它的每个结点并不储存一个数值,而是只有两种状态:安装与未安装。针对这一特点,我们对线段树的板子做以下修改即可:

  1. lazy标记:由于现在每个节点只有两种状态,\(lazy\)标记也只有两种数值就够了,比如\(1\)代表该区间所有软件全部被安装,\(-1\)代表该区间所有软件全部没有被安装。

  2. pushdown操作:之前\(pushdown\)仅仅把父节点的\(lazy\)标记下放即可,但现在\(lazy\)标记有两种,所以下放之前应当先判断:\(lazy\)标记为\(1\)则代表全部安装,那么就应当把子节点的安装数量变为区间内总节点数;\(lazy\)标记为\(1\)则代表全部卸载,那么就应当把子节点的安装数量变为\(0\)

  3. update操作:之前传参时传递的是向该区间内加的数,现在则应更改为操作模式,即卸载还是安装?安装则把\(lazy\)标记记为\(1\),把子节点的安装数量变为区间内总节点数;卸载则把\(lazy\)标记记为\(-1\),把子节点的安装数量变为\(0\)

  4. 其余操作均保持不变

代码

#include
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

幸甚至哉,歌以咏志。

你可能感兴趣的:([NOI2015]软件包管理器(巧用线段树))