[ NOI 2015 ] 软件包管理器


题目描述(luogu.org)
题目描述(codevs.cn)


题目大意:

这里有一些软件,下载它,必须先下载它的所有祖先;删除它,必须先删除它的所有儿子。
由于这是树形结构,而且又让进行序列操作,自然想到了树链剖分。数据范围( n<=10^5 )
树剖可过。


思路分析:

  • 怎么套线段树?
    看上去不好套,但是仔细想想:如果用1表示已安装,0表示未安装,那么整个树就是一个零一串。对于安装操作,就是把x点到1号点的数值全部变为1。对于删除操作,就是把它的子树都变为0。
  • 怎么算改变状态的数量?
    对于安装操作,先算出x点到1号点的和,这也就是有几个数为一。再用x点的深度,减去这个数值,即为这条路径上0的个数。
    对于删除操作,求出这个点子树和,还是这颗子树有一个树为一,即为答案。

具体细节看代码

代码

#include<iostream>
#include<cstdio>
using namespace std;
#define N 150000

int inp(){
	int Ans=0;bool o=false;char c;
	while((c=getchar())<'0'||c>'9')if(c=='-')o=true;Ans=c-48;
	while((c=getchar())>='0'&&c<='9')Ans=(Ans<<3)+(Ans<<1)+c-48;
	return o==true?-Ans:Ans;
}

struct Edge{int t,nxt;}edge[N<<1];
int tot,n,m,first[N];
int cnt,deep[N],fa[N],size[N],hson[N],top[N],id[N];
int sum[N<<2],add[N<<2];
char ch[20];

void build(int f,int t){
	edge[++tot]=(Edge){t,first[f]},first[f]=tot,
	edge[++tot]=(Edge){f,first[t]},first[t]=tot;
}

void dfs1(int x,int dad)
{
	fa[x]=dad,deep[x]=deep[dad]+1,size[x]=1;int maxson=-1;
	for(int i=first[x];i;i=edge[i].nxt)
	{
		int t=edge[i].t;
		if(t==dad)continue;
		dfs1(t,x);
		if(size[t]>maxson)maxson=size[t],hson[x]=t;
		size[x]+=size[t];
	}
}

void dfs2(int x,int topfa)
{
	id[x]=++cnt,top[x]=topfa;
	if(!hson[x])return;
	dfs2(hson[x],topfa);
	for(int i=first[x];i;i=edge[i].nxt)
	{
		int t=edge[i].t;
		if(t==fa[x]||t==hson[x])continue;
		dfs2(t,t);
	}
}

void pushup(int root){sum[root]=sum[root<<1]+sum[root<<1|1];}

void pushdown(int root,int ln,int rn)
{
	if(add[root]==1)
	{
		add[root<<1]=add[root<<1|1]=1;
		sum[root<<1]=ln,sum[root<<1|1]=rn;
		add[root]=0;
	}
	if(add[root]==2)
	{
		add[root<<1]=add[root<<1|1]=2;
		sum[root<<1]=0,sum[root<<1|1]=0;
		add[root]=0;
	}
}

int qsum(int L,int R,int l,int r,int root)
{
	if(L>R)swap(L,R);
	if(L<=l&&r<=R){int x=sum[root];sum[root]=r-l+1,add[root]=1;return x;}
	int mid=(l+r)>>1,ans=0;
	pushdown(root,mid-l+1,r-mid);
	if(L<=mid)ans+=qsum(L,R,l,mid,root<<1);
	if(R>mid)ans+=qsum(L,R,mid+1,r,root<<1|1);
	pushup(root);
	return ans;
}

int Qsum(int L,int R,int l,int r,int root)
{
	if(L>R)swap(L,R);
	if(L<=l&&r<=R){int x=sum[root];sum[root]=0,add[root]=2;return x;}
	int mid=(l+r)>>1,ans=0;
	pushdown(root,mid-l+1,r-mid);
	if(L<=mid)ans+=Qsum(L,R,l,mid,root<<1);
	if(R>mid)ans+=Qsum(L,R,mid+1,r,root<<1|1);
	pushup(root);
	return ans;
}

int main()
{
	n=inp();
	for(int t,i=1;i<n;i++)
		t=inp(),build(i+1,t+1);
	dfs1(1,0),dfs2(1,1);
	
	m=inp();
	for(int i=1,x,f;i<=m;i++)
	{
		scanf("%s",ch),f=x=inp(),f++,x++;
		if(ch[0]=='i')
		{
			int ans=0;
			while(top[x]!=top[1])
			{
				ans+=qsum(id[x],id[top[x]],1,n,1);
				x=fa[top[x]];
			}
			ans+=qsum(id[x],id[1],1,n,1);
			cout<<deep[f]-ans<<endl;
		}
		if(ch[0]=='u')
		{
			int ans=Qsum(id[x],id[x]+size[x]-1,1,n,1);
			cout<<ans<<endl;
		}
	}
}

总结

这个题的思路不难,也没有坑点,算是树剖入门变式题目。


NOIp2018 RP++
~

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值