1.20 【树链剖分】 NOI2015 软件包管理器

这篇博客介绍了如何使用树链剖分解决软件包管理器中的依赖问题。在Linux和OS X系统中,软件包管理器如apt-get和homebrew能自动处理软件依赖。文章详细阐述了设计一个软件包管理器的依赖解决程序,包括安装和卸载软件包时如何计算会改变的软件包数量。通过树链剖分,将树形结构转化为线性数组并利用线段树进行高效操作。
摘要由CSDN通过智能技术生成

Linux 用户和 OS X 用户一定对软件包管理器不会陌生。通过软件包管理器,你可以通过一行命令安装某一个软件包,然后软件包管理器会帮助你从软件源下载软件包,同时自动解决所有的依赖(即下载安装这个软件包的安装所依赖的其它软件包),完成所有的配置。Debian/Ubuntu 使用的 apt-get,Fedora/CentOS 使用的 yum,以及 OS X 下可用的 homebrew 都是优秀的软件包管理器。

你决定设计你自己的软件包管理器。不可避免地,你要解决软件包之间的依赖问题。如果软件包  A A 依赖软件包  B B,那么安装软件包  A A 以前,必须先安装软件包  B B。同时,如果想要卸载软件包  B B,则必须卸载软件包  A A。现在你已经获得了所有的软件包之间的依赖关系。而且,由于你之前的工作,除  0 0 号软件包以外,在你的管理器当中的软件包都会依赖一个且仅一个软件包,而  0 0 号软件包不依赖任何一个软件包。依赖关系不存在环(若有  m m  (m2) (m≥2) 个软件包  A1,A2,A3,,Am A1,A2,A3,…,Am,其中  A1 A1 依赖  A2 A2 A2 A2 依赖  A3 A3 A3 A3 依赖  A4 A4,……, Am1 Am−1 依赖  Am Am,而  Am Am 依赖  A1 A1,则称这  m m 个软件包的依赖关系构成环),当然也不会有一个软件包依赖自己。

现在你要为你的软件包管理器写一个依赖解决程序。根据反馈,用户希望在安装和卸载某个软件包时,快速地知道这个操作实际上会改变多少个软件包的安装状态(即安装操作会安装多少个未安装的软件包,或卸载操作会卸载多少个已安装的软件包),你的任务就是实现这个部分。注意,安装一个已安装的软件包,或卸载一个未安装的软件包,都不会改变任何软件包的安装状态,即在此情况下,改变安装状态的软件包数为  0 0

输入格式

输入文件的第  1 1 行包含  1 1 个正整数  n n,表示软件包的总数。软件包从  0 0 开始编号。

随后一行包含  n1 n−1 个整数,相邻整数之间用单个空格隔开,分别表示  1,2,3,,n2,n1 1,2,3,…,n−2,n−1 号软件包依赖的软件包的编号。

接下来一行包含  1 1 个正整数  q q,表示询问的总数。

之后  q q 行,每行  1 1 个询问。询问分为两种:

  • install  x x:表示安装软件包  x x
  • uninstall  x x:表示卸载软件包  x x

你需要维护每个软件包的安装状态,一开始所有的软件包都处于未安装状态。对于每个操作,你需要输出这步操作会改变多少个软件包的安装状态,随后应用这个操作(即改变你维护的安装状态)。

输出格式

输出文件包括  q q 行。

输出文件的第  i i 行输出  1 1 个整数,为第  i i 步操作中改变安装状态的软件包数。

input
7
0 0 0 1 1 5
5
install 5
install 6
uninstall 1
install 4
uninstall 0

output
3
1
3
2
3

蒟蒻的第一道树剖题。

每个软件只有一个依赖的软件,所以数据显然是一棵树。期中0号软件是Root.

安装软件就是将该点向上直至0节点全由0变成1,删除则是将一个子树全改成0.

一波树链剖分,按照重链优先的原则求dfs序,则可以将树状的结构转化为线性的数组(pos),且在一条重链上的各个点的pos是连续的。这样,将pos作为一个线段树的基础,那就可以通过这棵线段树来对一个区间(即一条重链)操作。

top[x]表示x所在重链的链顶。

pos[x]表示x在线段树最低一层的位置。

fa[x]表示x的父亲,即x软件直接依赖的软件的编号。

sons[x]表示以x为根的子树的节点数。

hson[x]表示x最重的儿子的编号。

gh[x]中存x的所有儿子。

uninstall时,ans=itree[now].sum,并将itree[now]的lazy值标记为0(全删)。

install,top[x]到x(即x所在重链中x及其之上的部分)全部改为1,则线段树中pos[top[x]]至pos[x]改为1,且ans+=itree[now].r-itree[now].l+1-itree[now].sum;再将x=fa[top[x]](即沿轻链向上走一步,找到下一个重链),直至x为0节点。

然后就可以搞了。然而我照着老师的代码改了两个半点。果然第一次写还是不要原创代码了。


#include<cstdio>
#include<queue>
#include<cstring>
#include<cmath>
#include<vector>
#include<iostream>
#include<algorithm>
#include<cstdlib>
using namespace std;
inline int read(){
	char c;int ret=0;
	do{c=getchar();}while(c>'9'||c<'0');
	do{ret=ret*10+c-48;c=getchar();}while(c>='0'&&c<='9');
	return ret;
}
struct node{
	int l,r,sum,ch;
};
const int man=100000;
int n,q;
int fa[man+10];
int sons[man+10],hson[man+10],pos[man+10],top[man+10];
vector<int> gh[man+10];
node itree[man*3+10];
int treecnt=0;
void input(){
	n=read();
	for(int i=1;i<=n-1;i++)
	{
		int t;
		t=read();
		fa[i]=t;
		gh[t].push_back(i);
	}
}
int dfs1(int x){
 //ruturn值为x为根节点的子树的节点个数
	int ha=1,bigone=0,big=0;//bigone是x最重的儿子,big是此儿子的重量
	for(int i=0;i<gh[x].size();i++)
	{
		int a=dfs1(gh[x][i]);
		ha+=a;
		if(a>big)
		{
			big=a;
			bigone=gh[x][i];
		}
	}
	sons[x]=ha;
	hson[x]=bigone;
	return ha;
}
int dfs2(int now) {
  treecnt++;
  pos[now] = treecnt;
  if (sons[now] == 1) return 0;
  top[hson[now]] = top[now];
  dfs2(hson[now]);
  for (int i = 0; i < gh[now].size(); i++)
    if (gh[now][i] != hson[now]) {
      top[gh[now][i]] = gh[now][i];
      dfs2(gh[now][i]);
    }
  return 0;
}
void build(){
 //先建立线段树的最底一层,然后由下至上建立线段树
  int k;
	for( k = 1 ; k < n ; k <<= 1 );
	n=k;
	for(int i=1;i<=n;i++)
	{
		itree[i+n-1].l=itree[i+n-1].r=i;
		itree[i+n-1].sum=0;
		itree[i+n-1].ch=2;
	}
	for(int i=n-1;i>=1;i--)
	{
		itree[i].l=itree[i*2].l;
		itree[i].r=itree[i*2+1].r;
		itree[i].sum=0;
		itree[i].ch=2;
	}
	return;
}
void init(){
	fa[0]=-1;
	dfs1(0);
	dfs2(0);
	build();
}
void push(int now){
	if(itree[now].ch==2)return;
	int lc=now*2;
	int rc=lc+1;
	itree[lc].ch=itree[now].ch;
	itree[rc].ch=itree[now].ch;
	itree[lc].sum=itree[lc].ch*(itree[lc].r-itree[lc].l+1);
	itree[rc].sum=itree[rc].ch*(itree[rc].r-itree[rc].l+1);
	itree[now].ch=2;
}
int add(int now,int x,int y,int op){
	int ret=0;
	if(itree[now].l>=x&&itree[now].r<=y)
	{
		if(op==1)ret=itree[now].r-itree[now].l+1-itree[now].sum;
		else ret=itree[now].sum;
		itree[now].ch=op;
		itree[now].sum=(itree[now].r-itree[now].l+1)*op;
		return ret;
	}
	push(now);
	if(itree[now*2].r>=x)ret+=add(now*2,x,y,op);
	if(itree[now*2+1].l<=y)ret+=add(now*2+1,x,y,op);
	itree[now].sum=itree[now*2].sum+itree[now*2+1].sum;
	return ret;
}
int install(int x){
	int ret=0;
	while(x!=-1)
	{
		ret+=add(1,pos[top[x]],pos[x],1);
		x=fa[top[x]];
	}
	return ret;
}
int uninstall(int x){
	return add(1,pos[x],pos[x]+sons[x]-1,0);
}
int main(){
	input();
	init();
	q=read();
	for(int i=1;i<=q;i++)
	{
		int t;
		char opp[15];
		scanf("%s%d",opp,&t);
		if(opp[0]=='i')
			printf("%d\n",install(t));
		else
			printf("%d\n",uninstall(t));
	}
	return 0;
}



LLAP.




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值