P2146 [NOI2015]软件包管理器 题解

37 篇文章 0 订阅

题目

一道树剖的变形题

跟普通树剖不一样的是,每次安装/ 卸载软件后,需要把区间中的值统一改为 0 / 1 0/1 0/1 。而这个也并不难实现

s u m [ k ] sum[k] sum[k] 表示区间 k k k 中已安装的软件个数( 1 1 1 的个数),并不需要再开一个数组维护 lazy-tag,可以理解为将求和和 lazy-tag 合并了

每次要将区间赋值 0 / 1 0/1 0/1 时,就直接将 s u m [ k ] sum[k] sum[k] 赋值为 0 / ( r − l + 1 ) 0/(r-l+1) 0/(rl+1),然后直接回溯。在访问的时候遇到 s u m [ k ] = 0 / ( r − l + 1 ) sum[k] = 0/(r-l+1) sum[k]=0/(rl+1) 时,就当做 lazy-tag 下放
关键代码:

inline void push_down(int k,int l,int r)
{
	int mid=(l+r)>>1;
	if(sum[k]==(r-l+1)) // 如果该区间是满的(全是1)
	{//他的子区间也必定是满的
		sum[k<<1]=(mid-l+1);
		sum[k<<1|1]=(r-mid);
	}
	else if(!sum[k]) // 反之亦然
	sum[k<<1]=sum[k<<1|1]=0;
}
void modify(int k,int l,int r,int x,int y,int v) // 将区间中所有元素赋值为v
{
	if(x<=l && r<=y){sum[k]=(r-l+1)*v;return;}
	push_down(k,l,r);
	int mid=(l+r)>>1;
	if(x<=mid)modify(k<<1,l,mid,x,y,v);
	if(mid<y)modify(k<<1|1,mid+1,r,x,y,v);
	push_up(k);
}
int query(int k,int l,int r,int x,int y,int v)// 询问等于v的元素个数
{
	if(x<=l && r<=y)
	{
		if(v)return sum[k];
		else return (r-l+1)-sum[k];
	}
	push_down(k,l,r);
	int mid=(l+r)>>1,ret=0;
	if(x<=mid)ret+=query(k<<1,l,mid,x,y,v);
	if(mid<y)ret+=query(k<<1|1,mid+1,r,x,y,v);
	return ret;
}

解决了这个问题之后,剩下的就是模板树剖啦
PS:代码里的节点编号为 1 ∼ n 1\sim n 1n

#include<cstdio>
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
const int Maxn=100000+10,Maxm=400000+10;
int sum[Maxm],add[Maxm];
int s[Maxn],id[Maxn],son[Maxn];
int top[Maxn],f[Maxn],d[Maxn];
int n,m,idcnt;
vector <int> e[Maxn];
inline void push_up(int k)
{
	sum[k]=sum[k<<1]+sum[k<<1|1];
}
inline void push_down(int k,int l,int r)
{
	int mid=(l+r)>>1;
	if(sum[k]==(r-l+1))
	{
		sum[k<<1]=(mid-l+1);
		sum[k<<1|1]=(r-mid);
	}
	else if(!sum[k])
	sum[k<<1]=sum[k<<1|1]=0;
}
inline int read()
{
	int s=0,w=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch>='0' && ch<='9')s=(s<<3)+(s<<1)+(ch^48),ch=getchar();
	return s*w;
}
void modify(int k,int l,int r,int x,int y,int v)
{
	if(x<=l && r<=y){sum[k]=(r-l+1)*v;return;}
	push_down(k,l,r);
	int mid=(l+r)>>1;
	if(x<=mid)modify(k<<1,l,mid,x,y,v);
	if(mid<y)modify(k<<1|1,mid+1,r,x,y,v);
	push_up(k);
}
int query(int k,int l,int r,int x,int y,int v)
{
	if(x<=l && r<=y)
	{
		if(v)return sum[k];
		else return (r-l+1)-sum[k];
	}
	push_down(k,l,r);
	int mid=(l+r)>>1,ret=0;
	if(x<=mid)ret+=query(k<<1,l,mid,x,y,v);
	if(mid<y)ret+=query(k<<1|1,mid+1,r,x,y,v);
	return ret;
}
void dfs1(int x,int fa)
{
	s[x]=1,f[x]=fa,d[x]=d[fa]+1;
	for(int i=0;i<e[x].size();++i)
	{
		int y=e[x][i];
		if(y==fa)continue;
		dfs1(y,x),s[x]+=s[y];
		if(s[y]>s[son[x]])son[x]=y;
	}
}
void dfs2(int x,int topp)
{
	top[x]=topp,id[x]=++idcnt;
	if(!son[x])return;
	dfs2(son[x],topp);
	for(int i=0;i<e[x].size();++i)
	{
		int y=e[x][i];
		if(y==f[x] || y==son[x])continue;
		dfs2(y,y);
	}
}
int ins(int x)// 将根节点到x的路径上的软件都装上
{
	int ret=0;
	while(top[x]!=1)
	{
		ret+=query(1,1,n,id[top[x]],id[x],0);
		modify(1,1,n,id[top[x]],id[x],1);
		x=f[top[x]];
	}
	ret+=query(1,1,n,id[1],id[x],0);
	modify(1,1,n,id[1],id[x],1);
	return ret;
}
int del(int x) // 将子树中已安装的全部删掉
{
	int ret=query(1,1,n,id[x],id[x]+s[x]-1,1);
	modify(1,1,n,id[x],id[x]+s[x]-1,0);
	return ret;
}
int main()
{
//	freopen("in.txt","r",stdin);
	n=read();
	for(int i=2;i<=n;++i)
	{
		int x=read()+1;
		e[x].push_back(i);
		e[i].push_back(x);
	}
	dfs1(1,0);
	dfs2(1,1);
	m=read();
	while(m--)
	{
		char str[10];
		scanf("%s",str);
		int x=read()+1;
		if(str[0]=='i')printf("%d\n",ins(x));
		else printf("%d\n",del(x));
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值