P2146 [NOI2015]软件包管理器

本文详细介绍了如何使用线段树和DFS序解决树形结构上的安装与卸载查询问题,通过具体代码展示了如何更新路径和子树的点权,实现了高效查询和修改操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一定要移一位啊!!!!!不然son[0] = 0无法记录重儿子啊!

install:就是这个点到根节点的路径上所有没有下载的点的和

uninstall:就是这个点的子树中所有下载的点的和;

install :查询完后,在dfs序上修改,使这条路所有点权为1;(线段树实现)

uninstall:这个点的子树中所有点修改为0 ;dfs序上区间修改

#include<iostream>
#include<cstdio>
#pragma GCC optimize(2)
#define RG register
using namespace std;
int n, m, flag[800005], tag[800005], dep[100005], top[100005], siz[100005], fa[100005], son[100005], in[100005], out[100005], idc, sum[800005];
char s[100];
int tp, tov[100005], nex[100005], h[100005];
void add(int x,int y)
{
    tp++;
    tov[tp] = y;
    nex[tp] = h[x];
    h[x] = tp;
}
void dfs(int x,int f)
{
    siz[x] = 1;
    fa[x] = f;
    for(RG int i = h[x]; i; i = nex[i])
    {
        int v = tov[i];
        if(v == f) continue;
        dep[v] = dep[x] + 1;
        dfs(v,x);
        siz[x] += siz[v];
        if(siz[v] > siz[son[x]]) son[x] = v;
    }
}
void dfs1(int x,int f)
{
    in[x] = ++idc;
    top[x] = f;
    if(son[x] > 0) dfs1(son[x],f);
    for(RG int i = h[x]; i; i = nex[i])
    {
        int  v = tov[i];
        if(v == fa[x] || v == son[x])
        {
            continue;
        }
        dfs1(v,v);
    }
    out[x] = idc;
}
void update(int o)
{
    sum[o] = sum[o << 1] + sum[o << 1 | 1];
}
void pushdown(int o, int l, int r)
{
    if(flag[o] == 1)
    {
        int mid = (l + r) >> 1; 
        flag[o << 1] = flag[o << 1 | 1] = 1;
        flag[o] = 0;
        sum[o << 1] = 1ll*(mid - l + 1) * tag[o];
        sum[o << 1 | 1] = 1ll*(r - mid) * tag[o];
        tag[o << 1] = tag[o << 1 | 1] = tag[o];
        tag[o] = 0;  
    }
}
inline int qurey(int o,int l,int r,int ql,int qr)
{
    pushdown(o, l, r);
    if(ql <= l && r <= qr)
    {
        return sum[o];
    }
    int mid = (l + r) >> 1;
    int rt = 0;
    if(ql <= mid) rt += qurey(o << 1,l,mid,ql,qr);
    if(mid < qr) rt += qurey(o << 1 | 1,mid+1,r,ql,qr);
    return rt; 
}
void modify(int o, int l, int r, int ql, int qr, int val)
{
    pushdown(o, l, r);
    if(ql <= l && r <= qr)
    {
        sum[o] = (r - l + 1) *val;
        tag[o] = val;
        flag[o] = 1;
        return;
    }
    int mid = (l + r) / 2;
    if(ql <= mid) modify(o << 1, l, mid, ql, qr, val);
    if(mid < qr) modify(o << 1 | 1, mid + 1, r, ql, qr, val);
    update(o);
}
int get(int u,int x)
{
    int rt = 0;
    while(top[u] != top[x])
    {
       if(dep[top[u]] < dep[top[x]]) swap(x,u);
        rt += qurey(1,1,idc,in[top[u]],in[u]);
        modify(1, 1, idc, in[top[u]], in[u],1);
        u = fa[top[u]];
    }
    if(dep[x] < dep[u]) swap(x,u);
    rt += qurey(1, 1, idc, in[u], in[x]);
    modify(1, 1, idc, in[u], in[x],1);	
    return  rt;
}
int main()
{
     scanf("%d",&n);;
    for(RG int i = 2; i <= n ; i++)
    {
        int x;
        scanf("%d",&x);
        x += 1;
        add(x,i);
    }
    dep[1] = 1;
    dfs(1,1);
    dfs1(1,0);
     scanf("%d",&m);
    for(RG int i = 1; i <= m; i++)
    {
        int a;
        scanf("%s",s);
         scanf("%d",&a);
        a++;
        if(s[0] == 'u')
        {
        	printf("%d\n",qurey(1, 1, idc, in[a], out[a]));
          	modify(1, 1, idc, in[a], out[a], 0);
        }
        else
        {
            int ha = get(1,a);
            printf("%d\n",dep[a] - ha)  ;
        }
    }
    return 0;
}

 

<think>我们正在解析NOI2015软件包管理器题目,使用树链剖分和线段树实现。题目描述:有n个软件包(编号0~n-1),它们之间有依赖关系形成一棵树(除0号外,每个包依赖一个父包)。初始所有软件包未安装。两种操作:installx:安装x以及x的依赖(从x到0号路径上所有未安装的包)uninstallx:卸载x以及x的依赖(x的子树中所有已安装的包)对于每个操作,输出操作会改变多少个软件包的安装状态(即从未安装变为安装,或从已安装变为未安装),然后应用操作。我们使用树链剖分将树转化为序列,然后用线段树维护区间和(安装状态为1,未安装为0)。这样:install操作:相当于从x到根节点的路径上未安装的节点全部安装。我们需要计算路径上未安装的节点数(即0的个数),然后全部置为1。uninstall操作:相当于将x的子树中已安装的节点全部卸载。我们需要计算子树中已安装的节点数(即1的个数),然后全部置为0。注意:树链剖分后,节点在序列中的位置用dfn序(DFS序)表示。子树对应区间[dfn[x],dfn[x]+size[x]-1];路径则通过树链剖分拆分成若干条重链上的区间。我们使用线段树维护区间和,同时支持区间覆盖(置0或置1)和区间求和。此外,我们还需要支持区间覆盖时,同时记录覆盖的值(0或1)以及懒标记。具体步骤:1.预处理:树链剖分(两次DFS)第一次DFS:求父节点、深度、子树大小、重儿子第二次DFS:求DFS序、重链顶点2.建线段树:根据dfn序,每个节点对应线段树的一个位置,初始值为0(未安装)。3.对于每个操作:installx:从x向上跳重链直到根节点,每次处理一条重链的区间。我们需要计算从x到根节点的路径上0的个数(即区间长度减去区间和),然后整个路径置1。改变的状态数=路径上0的个数。注意:跳重链时,将路径拆分成多个区间,从下往上(x到根)的区间是连续的,但要注意区间方向(dfn序从根往下递增,所以我们从x往上跳时,区间是从dfn[top[x]]到dfn[x]这样一段,然后x跳到fa[top[x]],继续处理)。uninstallx:直接处理子树:区间为[dfn[x],dfn[x]+sz[x]-1]改变的状态数=子树中1的个数(即区间和),然后置0。4.线段树需要实现:-区间覆盖(set)操作:将区间[l,r]覆盖为0或1,同时维护区间和。-区间求和(sum)操作。5.注意:在install操作中,我们进行路径查询和更新时,需要先查询路径上0的个数(即未安装的个数),然后执行覆盖为1。同样,uninstall操作先查询1的个数,然后覆盖为0。由于覆盖操作会改变区间的值,我们需要懒标记(lazy)来记录覆盖值(0或1)或者标记为无操作(例如-1)。代码结构:-定义树结构(邻接表)-树链剖分预处理-线段树类-主函数:读入数据、建树、剖分、处理操作下面我们给出C++代码解析,并添加详细注释。</think>### NOI2015 软件包管理器 C++ 代码解析(树链剖分+线段树) #### 问题分析 软件包管理器需要维护树形依赖结构(0号包为根节点),支持两种操作: 1. **install x**:安装x及所有依赖(x→根的路径) 2. **uninstall x**:卸载x及所有依赖(x的子树) 需要动态统计每次操作**改变的安装状态数量**(未安装→安装 或 已安装→未安装) #### 核心算法设计 ```mermaid graph TD A[树链剖分] --> B[重链划分] B --> C[DFS序映射] C --> D[线段树维护] D --> E[路径操作 install] D --> F[子树操作 uninstall] ``` #### 代码实现解析 ##### 1. 树链剖分预处理 ```cpp // 第一次DFS:求子树大小/重儿子 void dfs1(int u) { siz[u] = 1; for(int v : G[u]) { dep[v] = dep[u] + 1; // 深度 dfs1(v); siz[u] += siz[v]; // 子树大小 if(siz[v] > siz[son[u]]) son[u] = v; // 重儿子 } } // 第二次DFS:生成DFS序和重链 void dfs2(int u, int topf) { dfn[u] = ++cnt; // DFS序 top[u] = topf; // 重链顶端 if(son[u]) dfs2(son[u], topf); // 优先处理重儿子 for(int v : G[u]) { if(v == son[u]) continue; dfs2(v, v); // 新重链 } } ``` ##### 2. 线段树设计 ```mermaid classDiagram class SegmentTree { -sum[N<<2] : int // 区间和(已安装数量) -tag[N<<2] : int // 懒标记(-1未覆盖,0置0,1置1) +push_up(int rt) // 更新区间和 +push_down(int rt, int len) // 下传标记 +update(int L, int R, int l, int r, int rt, int val) // 区间覆盖 +query(int L, int R, int l, int r, int rt) : int // 区间查询 } ``` ##### 3. 关键操作实现 **install操作(路径覆盖)** ```cpp int install(int x) { int res = 0, org = x; while(top[x] != 0) { // 从x向根跳重链 // 查询路径上未安装数量 = 路径长度 - 已安装数量 res += (dfn[x] - dfn[top[x]] + 1) - tree.query(dfn[top[x]], dfn[x], 1, n, 1); tree.update(dfn[top[x]], dfn[x], 1, n, 1, 1); // 覆盖为安装状态 x = fa[top[x]]; // 跳到上条重链 } // 处理最后一段(根节点所在重链) res += (dfn[x] - dfn[0] + 1) - tree.query(dfn[0], dfn[x], 1, n, 1); tree.update(dfn[0], dfn[x], 1, n, 1, 1); return res; } ``` **uninstall操作(子树覆盖)** ```cpp int uninstall(int x) { // 查询子树中已安装数量 int res = tree.query(dfn[x], dfn[x]+siz[x]-1, 1, n, 1); tree.update(dfn[x], dfn[x]+siz[x]-1, 1, n, 1, 0); // 覆盖为未安装 return res; } ``` #### 时间复杂度分析 | 操作 | 时间复杂度 | 原理 | |-------------|------------|--------------------| | 树链剖分预处理 | $O(n)$ | 两次DFS | | install操作 | $O(\log^2 n)$ | 重链拆分+线段树操作 | | uninstall操作 | $O(\log n)$ | 子树区间操作 | #### 完整代码结构 ```cpp #include <vector> #include <cstring> using namespace std; const int N = 1e5+5; vector<int> G[N]; // 依赖树 int fa[N], dep[N], siz[N], son[N]; int dfn[N], top[N], cnt; SegmentTree tree; // 线段树 int main() { // 读入依赖关系 // 树链剖分预处理 // 处理q个操作: while(q--) { if(op == "install") cout << install(x) << endl; else cout << uninstall(x) << endl; } } ``` #### 树链剖分核心思想 将树拆分为**重链**,利用DFS序使: 1. 每条重链在DFS序上连续 2. 任意节点到根的路径被拆分为$O(\log n)$条重链 3. 子树对应DFS序上的连续区间 通过线段树高效处理**路径和子树**操作,将树操作转化为区间操作[^1][^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值