CCF-CSP认证考试 202403-5 文件夹合并 100分题解

更多 CSP 认证考试题目题解可以前往:CSP-CCF 认证考试真题题解


原题链接: 202403-5 文件夹合并

时间限制: 2.0 秒
空间限制: 512 MiB

题目描述

新入职西西艾弗岛有限公司的小 C 接替了刚刚升职的小 S 的项目。然而小 C 打开项目工程时,一层层嵌套的文件夹让小 C 感到眼花缭乱。为了精简项目结构,小 C 决定对项目的文件夹进行一些必要的合并。

项目中共有 n n n 个文件夹。为了方便,我们用 1 1 1 n n n 的整数给这 n n n 个文件夹编号,其中编号为 1 1 1 的文件夹为项目的根文件夹,其他每个文件夹都有一个父文件夹,这些文件夹构成了树形结构。除了子文件夹以外,第 i i i 个文件夹内还直接存储了 d i d_i di​ 字节的数据。

小 C 进行了若干次文件夹合并操作。每次操作中小 C 会选择一个文件夹 x j x_j xj​,将这个文件夹和它的所有子文件夹合并。具体地,小 C 会进行以下操作:遍历 x j x_j xj​ 的子文件夹 y y y,将文件夹 y y y 包含的所有文件夹和文件移动到文件夹 x j x_j xj,然后删除文件夹 y y y。所有文件和文件夹的名称是两两不同的,合并过程中不需要考虑文件或文件夹重名的情况。在每一次合并操作后,小 C 需要知道文件夹 x j x_j xj 内共有几个文件夹以及多少字节的数据。

例如,考虑以下项目:根文件夹内有文件夹 2 2 2 和文件夹 3 3 3 以及 100 100 100 字节数据,其中文件夹 2 2 2 为空文件夹,文件夹 3 3 3 内有 200 200 200 字节数据和文件夹 4 4 4,文件夹 4 4 4 包含 300 300 300 字节数据。对根文件夹进行一次合并后,文件夹 2 2 2 和文件夹 3 3 3 被合并至根文件夹,此时根文件夹下有文件夹 4 4 4 以及 300 300 300 字节数据,而文件夹 4 4 4 下也包含 300 300 300 字节数据。

在合并文件夹的过程中,小 C 常常需要访问某个文件夹 z j z_j zj 下的文件。此时,小 C 会从根文件夹开始,每次进入当前文件夹的一个子文件夹。小 C 需要知道按照以上过程,获取到文件夹 z j z_j zj 下的文件至少需要经过多少个文件夹。

例如,在以上项目中,未对根文件夹进行合并前,访问根文件夹下的文件只需要经过根文件夹一个文件夹,而访问文件夹 4 4 4 则需要经过根文件夹以及文件夹 3 3 3 4 4 4。而对根文件夹进行合并之后,访问文件夹 4 4 4 只需要经过根文件夹和文件夹 4 4 4 了。

在整个项目中,小 C 一共进行了 m m m 次文件夹合并以及文件访问操作。你需要帮助小 C 正确维护文件夹之间的关系,并在每次操作后正确回答小 C 需要的数据。

输入格式

从标准输入读入数据。

输入的第一行两个整数 n , m n,m n,m,分别表示文件夹数量以及操作次数。

第二行 ( n − 1 ) (n-1) (n1) 个整数 f 2 , ⋯   , f n f_2,\cdots,f_n f2,,fn​,其中 f i f_i fi 表示文件夹 i i i 的父文件夹编号。

第三行 n n n 个整数 d 1 , d 2 , ⋯   , d n d_1,d_2,\cdots,d_n d1,d2,,dn​,其中 d i d_i di 表示文件夹 i i i 中数据的存储量。

接下来 m m m 行第 j j j 行两个整数,第一个整数 o p j op_j opj 表示操作类型。若 o p j = 1 op_j = 1 opj=1 则表示一次文件夹合并操作,接下来一个整数 x j x_j xj 表示合并的文件夹编号;若 o p j = 2 op_j = 2 opj=2 则表示一次文件访问操作,接下来一个整数 z j z_j zj​ 表示访问的文件夹编号。

输出格式

输出到标准输出。

输出 m m m 行,第 j j j 行表示第 j j j 个操作中小 C 需要的数据:若 o p j = 1 op_j=1 opj=1 则输出两个整数,依次表示文件夹 x j x_j xj​ 的子文件夹数量以及数据的存储量;若 o p j = 2 op_j = 2 opj=2 则输出一个整数表示小 C 获取文件夹 z j z_j zj 下的数据最少需要经过的文件夹个数。

样例1输入

4 6
1 1 3
100 0 200 300
2 1
2 4
1 1
2 4
1 1
1 1

样例1输出

1
3
1 300
2
0 600
0 600

子任务

对于所有测试数据,

  • 1 ≤ n ≤ 5 × 1 0 5 , 1 ≤ m ≤ 3 × n 1 \leq n \leq 5 \times 10^{5}, 1 \leq m \leq 3 \times n 1n5×105,1m3×n
  • 1 ≤ f i ≤ n 1 \leq f_i \leq n 1fin,输入的文件夹结构构成树形结构,
  • 0 ≤ d i ≤ 1 0 5 0 \leq d_i \leq 10^{5} 0di105
  • 1 ≤ x j , z j ≤ n 1 \leq x_j, z_j \leq n 1xj,zjn,每次合并操作中给出的文件夹 x j x_j xj 没有被删除,每次文件访问操作中给出的文件夹 z j z_j zj​ 没有被删除。
子任务编号 n ≤ n \le n特殊性质分值
1 500 500 50010
2 5 , 000 5,000 5,00015
3 1 0 5 10^{5} 10515
4 5 × 1 0 5 5 \times 10^{5} 5×105A5
5 5 × 1 0 5 5 \times 10^{5} 5×105B5
6 5 × 1 0 5 5 \times 10^{5} 5×105C10
7 5 × 1 0 5 5 \times 10^{5} 5×105D15
8 5 × 1 0 5 5 \times 10^{5} 5×105E10
9 5 × 1 0 5 5 \times 10^{5} 5×10515

特殊性质 A: f i = ( i − 1 ) f_i = (i-1) fi=(i1)

特殊性质 B: f i = 1 f_i = 1 fi=1

特殊性质 C:在文件夹合并操作中, x j = 1 x_j = 1 xj=1

特殊性质 D: o p j = 1 op_j = 1 opj=1,即没有文件访问操作。

特殊性质 E: o p j = 2 op_j = 2 opj=2,即没有文件夹合并操作。


题解

维护子文件夹数量以及数据的存储量:对于合并文件夹操作 x x x,相当于把 x x x 所有儿子的儿子全部挂到 x x x 下,并把 x x x 的儿子的数据大小加到 x x x 中。这里使用链表来进行操作,对 x x x 的每个儿子只需要 O ( 1 ) \mathcal{O}(1) O(1) 的复杂度,从总体上看,一个节点最多被删 1 1 1 次,总的复杂度不会超过节点个数 O ( n ) \mathcal{O}(n) O(n)

维护获取文件夹下的数据最少需要经过的文件夹个数:等价于求该文件夹的深度。使用 dfs 序将所有节点进行重新编号,编号后,对于一个文件夹的所有子文件夹,dfs 序是连续的,记 i i i 文件夹的 dfs 序为 d f n i dfn_i dfni。预处理出每个文件夹的子文件夹的最小和最大 dfs 序,记为 l i , r i l_i,r_i li,ri。对于合并文件夹操作 x x x,相当于把 x x x 文件夹的所有子文件夹深度 − 1 -1 1,即将 [ l i , r i ] [l_i,r_i] [li,ri] 中的数 − 1 -1 1(由于不存在访问已经删除的文件夹,所有可以忽略这些文件夹的处理,直接进行区间减)。对于文件夹访问操作 z z z,直接查询该文件夹深度即可。这是一个经典的区间修改、单点查询问题,写棵线段树即可。

时间复杂度: O ( m log ⁡ n ) \mathcal{O}(m\log n) O(mlogn)

参考代码

/*
    Created by Pujx on 2024/5/8.
*/
#pragma GCC optimize(2, 3, "Ofast", "inline")
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
//#define int long long
//#define double long double
using i64 = long long;
using ui64 = unsigned long long;
using i128 = __int128;
#define inf (int)0x3f3f3f3f3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
#define yn(x) cout << (x ? "yes" : "no") << endl
#define Yn(x) cout << (x ? "Yes" : "No") << endl
#define YN(x) cout << (x ? "YES" : "NO") << endl
#define mem(x, i) memset(x, i, sizeof(x))
#define cinarr(a, n) for (int _ = 1; _ <= n; _++) cin >> a[_]
#define cinstl(a) for (auto& _ : a) cin >> _
#define coutarr(a, n) for (int _ = 1; _ <= n; _++) cout << a[_] << " \n"[_ == n]
#define coutstl(a) for (const auto& _ : a) cout << _ << ' '; cout << endl
#define all(x) (x).begin(), (x).end()
#define md(x) (((x) % mod + mod) % mod)
#define ls (s << 1)
#define rs (s << 1 | 1)
#define ft first
#define se second
#define pii pair<int, int>
#ifdef DEBUG
    #include "debug.h"
#else
    #define dbg(...) void(0)
#endif

const int N = 5e5 + 5;
//const int M = 1e5 + 5;
const int mod = 998244353;
//const int mod = 1e9 + 7;
//template <typename T> T ksm(T a, i64 b) { T ans = 1; for (; b; a = 1ll * a * a, b >>= 1) if (b & 1) ans = 1ll * ans * a; return ans; }
//template <typename T> T ksm(T a, i64 b, T m = mod) { T ans = 1; for (; b; a = 1ll * a * a % m, b >>= 1) if (b & 1) ans = 1ll * ans * a % m; return ans; }

int a[N];
int n, m, t, k, q;

int fa[N];
int head[N], tail[N], sz[N], to[N], nxt[N], cnt;
i64 d[N];

void add(int u, int v) {
    ++cnt;
    if (!sz[u]) tail[u] = cnt;
    to[cnt] = v;
    nxt[cnt] = head[u];
    head[u] = cnt;
    sz[u]++;
}
void merge(int u, int v) {
    if (!sz[v]) return;
    if (!sz[u]) head[u] = head[v];
    else nxt[tail[u]] = head[v];
    tail[u] = tail[v];
    sz[u] += sz[v];
}

int dep[N], l[N], r[N], tot;
vector<int> dfn;
void dfs(int u) {
    l[u] = ++tot;
    dfn.emplace_back(u);
    for (int i = head[u]; i; i = nxt[i]) {
        int v = to[i];
        dep[v] = dep[u] + 1;
        dfs(v);
    }
    r[u] = tot;
}
template <typename T> struct SegmentTree {
    struct TreeNode { int l, r; T add, st, sum, mx, mn; } tr[N << 2];
    void pushup(int s) {
        tr[s].sum = tr[ls].sum + tr[rs].sum;
        tr[s].mx = max(tr[ls].mx, tr[rs].mx);
        tr[s].mn = min(tr[ls].mn, tr[rs].mn);
    }
    void pushdown(int s) {
        if (tr[s].st != numeric_limits<T>::min()) {
            tr[s].st += tr[s].add;
            tr[ls].add = tr[rs].add = 0;
            tr[ls].st = tr[rs].st = tr[s].st;
            tr[ls].sum = tr[s].st * (tr[ls].r - tr[ls].l + 1);
            tr[rs].sum = tr[s].st * (tr[rs].r - tr[rs].l + 1);
            tr[ls].mx = tr[rs].mx = tr[s].st;
            tr[ls].mn = tr[rs].mn = tr[s].st;
            tr[s].st = numeric_limits<T>::min();
            tr[s].add = 0;
        }
        else if (tr[s].add) {
            tr[ls].add += tr[s].add;
            tr[rs].add += tr[s].add;
            tr[ls].sum += tr[s].add * (tr[ls].r - tr[ls].l + 1);
            tr[rs].sum += tr[s].add * (tr[rs].r - tr[rs].l + 1);
            tr[ls].mx += tr[s].add;
            tr[rs].mx += tr[s].add;
            tr[ls].mn += tr[s].add;
            tr[rs].mn += tr[s].add;
            tr[s].add = 0;
        }
    }
    void build(int l, int r, int s = 1) {
        tr[s].l = l, tr[s].r = r;
        tr[s].add = T(), tr[s].st = numeric_limits<T>::min();
        tr[s].sum = T(), tr[s].mx = numeric_limits<T>::min(), tr[s].mn = numeric_limits<T>::max();
        if (l == r) {
            tr[s].sum = tr[s].mx = tr[s].mn = dep[dfn[l - 1]];
            return;
        }
        int mid = l + r >> 1;
        if (l <= mid) build(l, mid, ls);
        if (mid < r) build(mid + 1, r, rs);
        pushup(s);
    }
    void update(int l, int r, T val, int s = 1) {
        if (l <= tr[s].l && tr[s].r <= r) {
            tr[s].add += val;
            tr[s].sum += val * (tr[s].r - tr[s].l + 1);
            tr[s].mx += val;
            tr[s].mn += val;
            return;
        }
        pushdown(s);
        int mid = tr[s].l + tr[s].r >> 1;
        if (l <= mid) update(l, r, val, ls);
        if (mid < r) update(l, r, val, rs);
        pushup(s);
    }
    void modify(int l, int r, T val, int s = 1) {
        if (l <= tr[s].l && tr[s].r <= r) {
            tr[s].add = T();
            tr[s].st = val;
            tr[s].sum = val * (tr[s].r - tr[s].l + 1);
            tr[s].mx = tr[s].mn = val;
            return;
        }
        pushdown(s);
        int mid = tr[s].l + tr[s].r >> 1;
        if (l <= mid) modify(l, r, val, ls);
        if (mid < r) modify(l, r, val, rs);
        pushup(s);
    }
    T query(int l, int r, int s = 1) {
        if (l <= tr[s].l && tr[s].r <= r) return tr[s].sum;
        int mid = tr[s].l + tr[s].r >> 1;
        T ans = T();
        pushdown(s);
        if (l <= mid) ans += query(l, r, ls);
        if (mid < r) ans += query(l, r, rs);
        return ans;
    }
    T queryMax(int l, int r, int s = 1) {
        if (l <= tr[s].l && tr[s].r <= r) return tr[s].mx;
        int mid = tr[s].l + tr[s].r >> 1;
        T ans = numeric_limits<T>::min();
        pushdown(s);
        if (l <= mid) ans = max(ans, queryMax(l, r, ls));
        if (mid < r) ans = max(ans, queryMax(l, r, rs));
        return ans;
    }
    T queryMin(int l, int r, int s = 1) {
        if (l <= tr[s].l && tr[s].r <= r) return tr[s].mn;
        int mid = tr[s].l + tr[s].r >> 1;
        T ans = numeric_limits<T>::max();
        pushdown(s);
        if (l <= mid) ans = min(ans, queryMin(l, r, ls));
        if (mid < r) ans = min(ans, queryMin(l, r, rs));
        return ans;
    }
};
SegmentTree<int> T;

void work() {
    cin >> n >> m;
    for (int i = 2; i <= n; i++) {
        cin >> fa[i];
        add(fa[i], i);
    }
    cinarr(d, n);
    dfs(1);
    T.build(1, n);

    while (m--) {
        int op, u;
        cin >> op >> u;
        if (op == 1) {
            int tmp = head[u];
            head[u] = tail[u] = sz[u] = 0;
            for (int i = tmp; i; i = nxt[i]) {
                int v = to[i];
                d[u] += d[v];
                merge(u, v);
            }
            cout << sz[u] << ' ' << d[u] << endl;
            if (l[u] + 1 <= r[u]) T.update(l[u] + 1, r[u], -1);
        }
        else cout << T.query(l[u], l[u]) + 1 << endl;
    }
}

signed main() {
#ifdef LOCAL
    freopen("C:\\Users\\admin\\CLionProjects\\Practice\\data.in", "r", stdin);
    freopen("C:\\Users\\admin\\CLionProjects\\Practice\\data.out", "w", stdout);
#endif
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int Case = 1;
    //cin >> Case;
    while (Case--) work();
    return 0;
}
/*
     _____   _   _       _  __    __
    |  _  \ | | | |     | | \ \  / /
    | |_| | | | | |     | |  \ \/ /
    |  ___/ | | | |  _  | |   }  {
    | |     | |_| | | |_| |  / /\ \
    |_|     \_____/ \_____/ /_/  \_\
*/

关于代码的亿点点说明:

  1. 代码的主体部分位于 void work() 函数中,另外会有部分变量申明、结构体定义、函数定义在上方。
  2. #pragma ... 是用来开启 O2、O3 等优化加快代码速度。
  3. 中间一大堆 #define ... 是我习惯上的一些宏定义,用来加快代码编写的速度。
  4. "debug.h" 头文件是我用于调试输出的代码,没有这个头文件也可以正常运行(前提是没定义 DEBUG 宏),在程序中如果看到 dbg(...) 是我中途调试的输出的语句,可能没删干净,但是没有提交上去没有任何影响。
  5. ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); 这三句话是用于解除流同步,加快输入 cin 输出 cout 速度(这个输入输出流的速度很慢)。在小数据量无所谓,但是在比较大的读入时建议加这句话,避免读入输出超时。如果记不下来可以换用 scanfprintf,但使用了这句话后,cinscanfcoutprintf 不能混用。
  6. main 函数和 work 函数分开写纯属个人习惯,主要是为了多组数据。
题目《202305-5 闪耀巡航》是CCF CSP(中国大学生程序设计竞赛)的一个挑战题目,它通常涉及算法、数据结构和数学思维。不过,由于你提到的是Python和C++的满题解,这说明我们需要考虑如何高效地使用这两种语言解决此问题。 闪耀巡航的问题描述一般涉及到路径优化、动态规划或者是图论中的最短路径问题。你需要帮助一个机器人在网格上找到从起点到终点的最优路线,可能会有光照限制或其他规则影响行进路径。 在Python中,可以利用Pandas处理二维数组,然后使用深度优先搜索(DFS)、广度优先搜索(BFS),或者更复杂一些,如Bellman-Ford算法来求解最短路径。记得检查边界条件和可能存在的循环引用问题。 C++方面,可以使用STL中的vector和list等容器,结合dfs函数或者Dijkstra算法(如果允许的话)。C++的迭代器和模板元编程能提高代码效率,但需要对数据结构和算法有深入理解。 以下是简化的步骤: 1. **析问题**:明确输入格式,理解光照、障碍物等因素的影响。 2. **数据结构**:用二维数组表示地图,定义状态转移方程。 3. **选择策略**:如果是简单的遍历,用DFS;复杂一点的情况,考虑动态规划或者图搜索算法。 4. **编写代码**:递归或迭代实现算法,注意优化边界处理和空间复杂度。 5. **测试**:用样例数据验证算法是否正确,并尝试多种情况验证其鲁棒性。 至于具体的代码,这里给出一个大致框架,但因为竞赛题目细节未提供,无法给出完整的解答: ```cpp #include <iostream> using namespace std; // 用二维数组表示地图,值代表路径长度 int map[grid_size][grid_size]; // 动态规划或搜索函数 int shortestPath(int startRow, int startCol) { // 代码实现... } int main() { // 输入读取、初始化地图 // 调用shortestPath函数并打印结果 cout << shortestPath(0, 0) << endl; return 0; } ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值