洛谷 P2146 [NOI2015]软件包管理器
1 P2146 [NOI2015]软件包管理器
2 题目描述
时间限制 \(1s\) | 空间限制 \(125M\)
你决定设计你自己的软件包管理器。不可避免地,你要解决软件包之间的依赖问题。如果软件包 \(a\) 依赖软件包 \(b\),那么安装软件包 \(a\) 以前,必须先安装软件包 \(b\)。同时,如果想要卸载软件包 \(b\),则必须卸载软件包 \(a\)。
现在你已经获得了所有的软件包之间的依赖关系。而且,由于你之前的工作,除 \(0\) 号软件包以外,在你的管理器当中的软件包都会依赖一个且仅一个软件包,而 \(0\) 号软件包不依赖任何一个软件包。且依赖关系不存在环(即不会存在 \(m\) 个软件包 \(a_1,a_2, \dots, a_m\),对于 $i <m $, \(a_i\) 依赖 \(a_i+1\) 的情况)。
现在你要为你的软件包管理器写一个依赖解决程序。根据反馈,用户希望在安装和卸载某个软件包时,快速地知道这个操作实际上会改变多少个软件包的安装状态(即安装操作会安装多少个未安装的软件包,或卸载操作会卸载多少个已安装的软件包),你的任务就是实现这个部分。
注意,安装一个已安装的软件包,或卸载一个未安装的软件包,都不会改变任何软件包的安装状态,即在此情况下,改变安装状态的软件包数为 \(0\)。
数据范围:\(n\) 表示软件包个数,\(q\) 表示操作个数。\(n \le 100000, q \le 1000000\)。
3 题解
我们发现,如果把两个软件包的依赖关系表示为一条有向边,那么最终呈现出来的一定是一棵树,其中顶点 \(0\) 的出度为 \(0\),正如题目下方给出的树一样。那么此时,如果我们要安装 \(x\),那么所有 \(x\) 到 \(0\) 的路径上的软件包都被必须安装。如果我们要卸载 \(x\),那么所有能走到它的,也就是 \(x\) 的子树中的所有软件包都必须被卸载。这里为了便于深搜,我们反向建边,即从第 \(i\) 号软件包所依赖的软件包连向 \(i\)。这里我们用 \(0\) 表示没有被安装,\(1\) 表示已经被安装,于是每个节点就有了 \(1\) 和 \(0\) 两种权值。
这样一来,题目就被简化为:给出一棵树和两种操作,第一种操作是输出 \(x\) 号节点到根的路径上所有节点的个数与权值之和的差,并将所有节点的权值都赋为 \(1\);第二种操作是输出 \(x\) 号节点的子树中所有权值之和,并将所有子树中的节点的权值都赋为 \(0\)。
这里我们如果直接 \(dfs\) 硬性修改,时间复杂度为 \(O(nq)\),无法通过此题。这里我们考虑用树链剖分来解决这个问题。
树链剖分大致是一种通过特殊处理,将树上特殊链的节点序号变为连续,使得我们可以用数据结构进行维护的一种神奇算法(具体内容还请大家自行学习,作者表达能力不好,很难正确地将算法模板讲述清晰)。
这里加上树链剖分后的时间复杂度为 \(O(q \space log_2^2 n)\),可以接受。
4 代码(空格警告):
#include <iostream>
#include <cstdio>
using namespace std;
const int N = 1e5+10;
int n, q, u, tot, cnt;
int head[N], ver[N], last[N];
int id[N], top[N], dep[N], siz[N], f[N], son[N];
string opt;
struct node
{
int l, r, sum, tag;
}t[N*4];
void build(int p, int l, int r)
{
t[p].l = l;
t[p].r = r;
t[p].sum = 0;
t[p].tag = -1;
if (l == r) return ;
int mid = (l+r)/2;
build(p*2, l, mid);
build(p*2+1, mid+1, r);
}
void pushdown(int p)
{
if (t[p].tag != -1)
{
t[p*2].sum = (t[p*2].r - t[p*2].l + 1) * t[p].tag;
t[p*2+1].sum = (t[p*2+1].r - t[p*2+1].l + 1) * t[p].tag;
t[p*2].tag = t[p].tag;
t[p*2+1].tag = t[p].tag;
t[p].tag = -1;
}
}
void modify(int p, int l, int r, int d)
{
if (t[p].l >= l && t[p].r <= r)
{
t[p].sum = (t[p].r - t[p].l + 1) * d;
t[p].tag = d;
return ;
}
pushdown(p);
int mid = (t[p].l + t[p].r) / 2;
if (l <= mid) modify(p*2, l, r, d);
if (r > mid) modify(p*2+1, l, r, d);
t[p].sum = t[p*2].sum + t[p*2+1].sum;
}
int query(int p, int l, int r)
{
if (t[p].l >= l && t[p].r <= r) return t[p].sum;
pushdown(p);
int mid = (t[p].l + t[p].r) / 2, ans = 0;
if (l <= mid) ans += query(p*2, l, r);
if (r > mid) ans += query(p*2+1, l, r);
return ans;
}
void add(int x, int y)
{
ver[++tot] = y;
last[tot] = head[x];
head[x] = tot;
}
void dfs1(int x, int fa, int depth)
{
f[x] = fa;
dep[x] = depth;
siz[x] = 1;
for (int i = head[x]; i; i = last[i])
{
int y = ver[i];
dfs1(y, x, depth+1);
siz[x] += siz[y];
if (siz[y] > siz[son[x]]) son[x] = y;
}
}
void dfs2(int x, int t)
{
id[x] = ++cnt;
top[x] = t;
if (!son[x]) return ;
dfs2(son[x], t);
for (int i = head[x]; i; i = last[i])
{
int y = ver[i];
if (y == son[x]) continue;
dfs2(y, y);
}
}
int sum(int x, int y)
{
int fx = top[x], fy = top[y], ans = 0;
while (fx != fy)
{
if (dep[fx] >= dep[fy])
{
ans += query(1, id[fx], id[x]);
x = f[fx];
fx = top[x];
}
else
{
ans += query(1, id[fy], id[y]);
y = f[fy];
fy = top[y];
}
}
if (dep[x] <= dep[y]) ans += query(1, id[x], id[y]);
else ans += query(1, id[y], id[x]);
return ans;
}
void change(int x, int y, int d)
{
int fx = top[x], fy = top[y];
while (fx != fy)
{
if (dep[fx] >= dep[fy])
{
modify(1, id[fx], id[x], d);
x = f[fx];
fx = top[x];
}
else
{
modify(1, id[fy], id[y], d);
y = f[fy];
fy = top[y];
}
}
if (dep[x] <= dep[y]) modify(1, id[x], id[y], d);
else modify(1, id[y], id[x], d);
}
int main()
{
scanf("%d", &n);
build(1, 1, n);
for (int i = 2; i <= n; i++)
{
scanf("%d", &u);
u++;
add(u, i);
}
dfs1(1, 0, 1);
dfs2(1, 1);
scanf("%d", &q);
int x;
for (int i = 1; i <= q; i++)
{
cin >> opt;
scanf("%d", &x);
x++;
if (opt == "install")
{
printf("%d\n", dep[x] - sum(x, 1));
change(x, 1, 1);
}
else
{
printf("%d\n", query(1, id[x], id[x] + siz[x] - 1));
modify(1, id[x], id[x] + siz[x] - 1, 0);
}
}
return 0;
}