题解:洛谷 P3605 Promotion Counting

【题目来源】

洛谷:P3605 [USACO17JAN] Promotion Counting P - 洛谷

【题目描述】

奶牛们又一次试图创建一家创业公司,还是没有从过去的经验中吸取教训——牛是可怕的管理者!

为了方便,把奶牛从 \(1\sim n\) 编号,把公司组织成一棵树,1 号奶牛作为总裁(这棵树的根节点)。除了总裁以外的每头奶牛都有一个单独的上司(它在树上的 “双亲结点”)。

所有的第 \(i\) 头牛都有一个不同的能力指数 \(p_i\),描述了她对其工作的擅长程度。如果奶牛 \(i\) 是奶牛 \(j\) 的祖先节点,那么我们把奶牛 \(j\) 叫做 \(i\) 的下属。

不幸地是,奶牛们发现经常发生一个上司比她的一些下属能力低的情况,在这种情况下,上司应当考虑晋升她的一些下属。你的任务是帮助奶牛弄清楚这是什么时候发生的。简而言之,对于公司的中的每一头奶牛 \(i\),请计算其下属 \(j\) 的数量满足 \(p_j > p_i\)

【输入】

输入的第一行包括一个整数 \(n\)

接下来的 \(n\) 行包括奶牛们的能力指数 \(p_1,p_2 \dots p_n\)。保证所有数互不相同。

接下来的 \(n-1\) 行描述了奶牛 \(2 \sim n\) 的上司的编号。再次提醒,1 号奶牛作为总裁,没有上司。

【输出】

输出包括 \(n\) 行。输出的第 \(i\) 行应当给出有多少奶牛 \(i\) 的下属比奶牛 \(i\) 能力高。

【输入样例】

5
804289384
846930887
681692778
714636916
957747794
1
1
2
3

【输出样例】

2
0
1
0
0

【核心思想】

  1. 问题分析:给定一棵以 \(1\) 为根的树,每个节点有权值 \(p_i\)(互不相同)。对每个节点 \(i\),求其子树中权值大于 \(p_i\) 的节点数量。这是一个树状数组 + DFS序/子树统计问题,关键在于利用 DFS 的进入-离开时间戳,通过树状数组统计子树范围内的权值分布。

  2. 算法选择

    • 权值离散化:将大范围的权值 \(p_i\) 映射到 \(1\)\(n\) 的连续整数,便于树状数组处理
    • DFS + 树状数组:在 DFS 过程中,利用树状数组维护当前已访问节点的权值分布
    • 差分统计:进入节点 \(x\) 时记录"当前全局比 \(p_x\) 大的数量",离开时再记录一次,两次差值即为子树内比 \(p_x\) 大的数量(因为子树节点在两次记录之间被访问并加入树状数组)
  3. 关键步骤

    • 读取输入\(n\),权值 \(p[1..n]\),树结构(父节点关系)
    • 离散化:排序去重后将 \(p_i\) 映射到 \([1, n]\)
    • 建树:根据父节点关系建立有向边
    • DFS(从根节点 \(1\) 开始):
      • 进入时\(ans[x] \mathrel{-}= query(n) - query(p[x])\)(记录当前全局比 \(p_x\) 大的数量)
      • 递归子节点:遍历所有子节点并 DFS
      • 离开时\(ans[x] \mathrel{+}= query(n) - query(p[x])\)(再次记录,差值即为子树新增的比 \(p_x\) 大的数量)
      • 加入树状数组\(add2(p[x], 1)\)(将当前节点权值加入统计)
    • 输出 \(ans[1..n]\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n \log n)\),离散化 \(O(n \log n)\),DFS 中每个节点两次树状数组操作 \(O(\log n)\)
    • 空间复杂度:\(O(n)\),树结构、树状数组和答案数组
  5. 树状数组 + DFS 的核心思想

    • 进入-离开差分:利用 DFS 的时间顺序,子树内所有节点都在"进入"和"离开"之间被访问。两次查询的差值恰好是子树内满足条件的节点数
    • 树状数组动态维护:树状数组维护的是"当前已处理路径上的权值分布",DFS 回溯时自然体现子树范围
    • 离散化降维:将大权值映射到小范围,使树状数组空间可控
    • 适用于"子树权值统计 + 动态维护"类问题,核心是利用 DFS 序和差分思想将子树问题转化为区间统计

【算法标签】

提高+ #树状数组

【代码详解】

#include <bits/stdc++.h>
using namespace std;

const int N = 100005;  // 最大节点数

// 邻接表存储树结构
int h[N];     // 头节点数组,h[i]表示节点i的第一条边
int e[N];     // 边数组,e[i]存储边的终点
int ne[N];    // 下一个边数组,ne[i]存储下一条边的索引
int idx;      // 边索引计数器

int n;        // 节点数量
int p[N];     // p[i]: 节点i的权值
int b[N];     // b[]: 用于离散化的临时数组
int tr[N];    // 树状数组,用于统计权值出现次数
int ans[N];   // ans[i]: 节点i的答案(逆序对数量)

/**
 * 添加有向边 a -> b
 * @param a 起点
 * @param b 终点
 */
void add(int a, int b)
{
    e[++idx] = b;        // 存储终点
    ne[idx] = h[a];       // 新边指向原链表头
    h[a] = idx;          // 更新链表头
}

/**
 * 计算lowbit:获取x的最低位的1
 * @param x 输入数值
 * @return x的最低位的1所代表的值
 */
int lowbit(int x)
{
    return x & -x;  // 利用补码性质
}

/**
 * 树状数组单点更新操作
 * @param x 更新位置
 * @param c 增加的值
 */
void add2(int x, int c)
{
    // 树状数组标准更新操作
    for (int i = x; i <= n; i += lowbit(i))
        tr[i] += c;
}

/**
 * 树状数组前缀和查询操作
 * @param x 查询位置
 * @return 前x个位置的和
 */
int query(int x)
{
    int res = 0;
    // 树状数组标准查询操作
    for (int i = x; i; i -= lowbit(i))
        res += tr[i];
    return res;
}

/**
 * 深度优先搜索(DFS)计算每个节点的逆序对数量
 * 使用树状数组统计当前路径上的权值分布
 * @param x 当前节点
 */
void dfs(int x)
{
    // 进入节点x时:记录当前比p[x]大的权值数量
    // query(n) - query(p[x]) 表示比p[x]大的权值数量
    ans[x] -= (query(n) - query(p[x]));

    // 递归处理所有子节点
    for (int i = h[x]; i; i = ne[i])
        dfs(e[i]);

    // 离开节点x时:再次统计比p[x]大的权值数量
    // 两次统计的差值就是x的子树中比p[x]大的节点数量
    ans[x] += (query(n) - query(p[x]));

    // 将当前节点的权值加入树状数组
    add2(p[x], 1);
}

int main()
{
    // 输入节点数量
    cin >> n;

    // 读入每个节点的权值
    for (int i = 1; i <= n; i++)
    {
        cin >> p[i];
        b[i] = p[i];  // 复制权值用于离散化
    }

    // 对权值进行排序,准备离散化
    sort(b + 1, b + n + 1);

    // 离散化:将权值映射到1~n的连续整数
    for (int i = 1; i <= n; i++)
        p[i] = lower_bound(b + 1, b + n + 1, p[i]) - b;

    // 读入树结构(n-1条边)
    for (int i = 2; i <= n; i++)
    {
        int x;
        cin >> x;  // 输入父节点
        add(x, i); // 添加边 x -> i
    }

    // 从根节点1开始深度优先搜索
    dfs(1);

    // 输出每个节点的答案
    for (int i = 1; i <= n; i++)
        cout << ans[i] << endl;

    return 0;
}

【运行结果】

5
804289384
846930887
681692778
714636916
957747794
1
1
2
3
2
0
1
0
0
posted @ 2026-08-28 15:57  团爸讲算法  阅读(5)  评论(0)    收藏  举报