AcWing 918 软件包管理器 (树链剖分+线段树)

Linux 用户和 OSX 用户一定对软件包管理器不会陌生。

通过软件包管理器,你可以通过一行命令安装某一个软件包,然后软件包管理器会帮助你从软件源下载软件包,同时自动解决所有的依赖(即下载安装这个软件包的安装所依赖的其它软件包),完成所有的配置。

Debian/Ubuntu 使用的 apt-get,Fedora/CentOS 使用的 yum,以及 OSX 下可用的 homebrew 都是优秀的软件包管理器。

你决定设计你自己的软件包管理器。

不可避免地,你要解决软件包之间的依赖问题。

如果软件包 A 依赖软件包 B,那么安装软件包 A 以前,必须先安装软件包 B。

同时,如果想要卸载软件包 B,则必须卸载软件包 A。

现在你已经获得了所有的软件包之间的依赖关系。

而且,由于你之前的工作,除 0 号软件包以外,在你的管理器当中的软件包都会依赖一个且仅一个软件包,而 0 号软件包不依赖任何一个软件包。

依赖关系不存在环(若有 m(m≥2) 个软件包 A1,A2,A3,…,Am,其中 A1 依赖 A2,A2 依赖 A3,A3 依赖 A4,……,Am−1 依赖 Am,而 Am 依赖 A1,则称这 m 个软件包的依赖关系构成环),当然也不会有一个软件包依赖自己。

现在你要为你的软件包管理器写一个依赖解决程序。

根据反馈,用户希望在安装和卸载某个软件包时,快速地知道这个操作实际上会改变多少个软件包的安装状态(即安装操作会安装多少个未安装的软件包,或卸载操作会卸载多少个已安装的软件包),你的任务就是实现这个部分。

注意,安装一个已安装的软件包,或卸载一个未安装的软件包,都不会改变任何软件包的安装状态,即在此情况下,改变安装状态的软件包数为 0。

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

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

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

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

install x:表示安装软件包 x
uninstall x:表示卸载软件包 x
你需要维护每个软件包的安装状态,一开始所有的软件包都处于未安装状态。

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

输出格式
输出文件包括 q 行。

输出文件的第 i 行输出 1 个整数,为第 i 步操作中改变安装状态的软件包数。
输入样例1:
7
0 0 0 1 1 5
5
install 5
install 6
uninstall 1
install 4
uninstall 0
输出样例1:
3
1
3
2
3

开始口胡:
很明显,题目中软件的关系可以构成一个树形结构,每个节点有一个值,1代表安装了,0代表没安装。安装程序u就是将u到根节点路径上的所有点都赋成1,卸载程序u就是将u及其子树上的节点赋为0
直接在树上进行统计是十分麻烦且耗时的,所以我们可以考虑重链剖分,将树转化成一个数组,再在数组上建立线段树,即可快速操作
以样例来画个图请添加图片描述
一个节点有很多个儿子,儿子中子节点最多的儿子叫重儿子,比如2号点是1号点的重儿子,6号点是二号点的重儿子。而由重儿子生成的一条链就叫重链(1->2->6->7)
当我们在用dfs序遍历树时,我们优先遍历重儿子所在的链
如果用正常dfs遍历树,最后生成的数组是:
1 2 5 6 7 3 4
重链优先遍历树,生成的数组是
1 2 6 7 5 3 4
随后正常建线段树
请添加图片描述
第一个操作:安装软件5
及 即将5号点到1号点的值赋为1
id[1]=1 id[5]=5
由上图可知,1号点和5号点不在同一个重链上,所以我们要先将5号点移动到1号点所在的链上
5号点所在的链的顶端就是5号点本身,所以更新路径5->5
映射在线段树中,就是把5号点赋为1
随后5号点就可以跳跃到其链顶端的父节点(2号点)上
此时2号点与1号点在同一个链上,即直接更新路径从id[2]更新到id[1],映射在线段树中就是将区间1到2中的点赋1
又例如删除操作:
假设其他点都是1,先要删除2号点
已知id[2]=2,2号点的子树中一共有3个点,所以只需要更新区间id[2]到id[2]+3-1,(由dfs序建树的特性可知,一个点的dfs序加上其子树大小就包括了其子树的dfs序)

#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
int n, m;
vector<int>g[N];
int dep[N];//深度
int sz[N];//子树大小
int top[N];//链的顶点
int fa[N];//父节点
int son[N];//重儿子
int id[N], cnt; //dfs序

struct Tree {
    
    
	//flag懒标记 sum区间中1的个数(安装的数量)
	int l, r, flag, sum;
} tr[N * 4];

void dfs1(int u, int depth) {
    
    
	dep[u] = depth, sz[u] = 1;
	for (auto i : g[u]) {
    
    
		dfs1(i, depth + 1);
		sz[u] += sz[i];
		if (sz[son[u]] < sz[i])
			son[u] = i;
	}

}

void dfs2(int u, int t) {
    
     //当前点编号 当前点所在重链顶点
	id[u] = ++cnt;
	top[u] = t;
	if (!son[u])
		return;
	dfs2(son[u], t);
	for (auto i : g[u]) {
    
    
		if (i == son[u])
			continue;
		dfs2(i, i); //当前轻儿子所在重链顶点是它自己
	}

}

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

void pushdown(int u) {
    
    
	auto &root = tr[u], &left = tr[u << 1], &right = tr[u << 1 | 1];
	if (root.flag != -1) {
    
    
		left.sum = root.flag * (left.r - left.l + 1);
		right.sum = root.flag * (right.r - right.l + 1);
		left.flag = right.flag = root.flag;
		root.flag = -1;
	}
}

void build(int u, int l, int r) {
    
    
	tr[u] = {
    
    l, r, -1, 0}; //最开始所有软件都是未安装状态
	if (l == r)
		return;
	int mid = l + r >> 1;
	build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
}

void update(int u, int l, int r, int k) {
    
    
	if (l <= tr[u].l && r >= tr[u].r) {
    
    
		tr[u].flag = k;
		tr[u].sum = k * (tr[u].r - tr[u].l + 1);
		return ;
	}
	int mid = tr[u].l + tr[u].r >> 1;
	pushdown(u);
	if (l <= mid)
		update(u << 1, l, r, k);
	if (r > mid)
		update(u << 1 | 1, l, r, k);
	pushup(u);
}

void update_path(int u, int v, int k) {
    
    
	while (top[u] != top[v]) {
    
    
		if (dep[top[u]] < dep[top[v]]) {
    
    
			swap(u, v);
		}
		update(1, id[top[u]], id[u], k);
		u = fa[top[u]];
	}
	if (dep[u] < dep[v]) {
    
    
		swap(u, v);
	}
	update(1, id[v], id[u], k);
}

void update_tree(int u, int k) {
    
    
	update(1, id[u], id[u] + sz[u] - 1, k);
}

int main() {
    
    
	cin >> n;
	for (int i = 2; i <= n; i++) {
    
    
		int p;
		cin >> p;
		p++;//下标从1开始,转化一下
		g[p].push_back(i);
		fa[i] = p;
	}
	dfs1(1, 1); //求深度,重儿子
	dfs2(1, 1); //找重链,用id数组将树剖成一个数组
	build(1, 1, n);
	cin >> m;
	string op;
	int u;
	while (m--) {
    
    
		cin >> op >> u;
		u++;
		if (op == "install") {
    
    
			int t = tr[1].sum; //原本的总和
			update_path(1, u, 1); //从1号点到u点变成1
			cout << tr[1].sum - t << endl;
		} else {
    
    
			int t = tr[1].sum;
			update_tree(u, 0); //将以u为根的子树全变成0
			cout << t - tr[1].sum << endl;
		}
	}
}





Guess you like

Origin blog.csdn.net/fdxgcw/article/details/120078997