1 条题解

  • 0
    @ 2025-10-14 17:50:01

    「SDOI2017」天才黑客 详细题解

    问题深入分析

    1. 核心难点

    • 动态口令:程序的口令在运行过程中会改变
    • LCP代价:通过边的代价与当前口令和边口令的相似度相关
    • 状态爆炸:直接维护(节点, 口令)的状态数达到 O(n×k)O(n \times k),不可行

    2. 关键转化

    观察1:在字典树中,从节点 xx 到节点 yy 的路径代价只与它们的LCA有关

    • cost=c+dep[LCA(x,y)]\text{cost} = c + \text{dep}[LCA(x,y)]
    • 这是因为LCP长度等于LCA的深度

    观察2:我们可以把问题看作在分层图上跑最短路

    • 第一层:原图的节点
    • 第二层:字典树节点(代表不同口令)
    • 但直接构建这样的图会太大

    算法核心思想

    1. 虚树优化建图

    对于每个原图节点 uu,我们只关心:

    • uu 出发的边对应的字典树节点(出边口令)
    • 到达 uu 的边对应的字典树节点(入边口令)

    步骤

    1. 收集所有与 uu 相关的字典树节点
    2. 构建这些节点的虚树(包含所有LCA)
    3. 在虚树上建立辅助结构来快速计算最小代价

    2. 代价计算优化

    对于从口令 xx 切换到口令 yy 的代价 dep[LCA(x,y)]\text{dep}[LCA(x,y)],我们利用虚树性质:

    在虚树中,对于任意节点 yy,考虑所有可能的 xx

    min_{x} (dist[u][x] + dep[LCA(x,y)]) = min_{z在y到根的路径上} (f(z) + dep[z])
    

    其中 f(z)=minxz子树中dist[u][x]f(z) = min_{x在z子树中} dist[u][x]

    3. 前后缀链技术

    为了实现快速查询,我们在虚树上构建:

    • 前缀链 pre[i]pre[i]:从虚树第一个节点到第 ii 个节点的最优值
    • 后缀链 suf[i]suf[i]:从第 ii 个节点到虚树最后一个节点的最优值

    这样对于任意 yy,我们可以在 O(1)O(1) 时间内求出最小代价。

    详细实现步骤

    阶段1:预处理字典树

    // 树链剖分预处理LCA
    void dfs1(int u) {
        sz[u] = 1;
        for (每个子节点 v of u) {
            dep[v] = dep[u] + 1;
            dfs1(v);
            sz[u] += sz[v];
            if (sz[v] > sz[bigson[u]]) bigson[u] = v;
        }
    }
    
    void dfs2(int u) {
        dfn[u] = ++dfc;
        if (bigson[u]) {
            top[bigson[u]] = top[u];
            dfs2(bigson[u]);
        }
        for (每个子节点 v of u, v ≠ bigson[u]) {
            top[v] = v;
            dfs2(v);
        }
    }
    

    阶段2:为每个节点构建优化图

    步骤2.1:收集相关字典树节点

    • 添加根节点 1
    • 添加所有出边的口令节点
    • 添加所有入边的口令节点

    步骤2.2:构建虚树

    1. 按DFS序排序
    2. 添加相邻节点的LCA
    3. 再次排序去重

    步骤2.3:建立辅助结构

    // 为虚树节点分配编号
    for j = 1 to cnt:
        id[j] = new_node()
        mxn[j] = mnn[j] = j
    
    // 构建前后缀链
    pre[1] = new_node(); link(pre[1], id[1], 0)
    suf[cnt] = new_node(); link(suf[cnt], id[cnt], 0)
    
    for j = 2 to cnt:
        pre[j] = new_node()
        link(pre[j], id[j], 0)
        link(pre[j], pre[j-1], 0)  // 前缀传递
    
    for j = cnt-1 downto 1:
        suf[j] = new_node()
        link(suf[j], id[j], 0)
        link(suf[j], suf[j+1], 0)  // 后缀传递
    

    步骤2.4:建立LCP优化边

    for j = cnt downto 2:
        fa = Find(LCA(x[j], x[j-1]))
        mxn[fa] = max(mxn[fa], mxn[j])
        mnn[fa] = min(mnn[fa], mnn[j])
        
        if (fa ≠ 1) link(id[j], id[fa], 0)
        
        if (mnn[j] ≠ 1) 
            link(id[j], pre[mnn[j]-1], dep[x[fa]])
            
        if (mxn[j] ≠ cnt)
            link(id[j], suf[mxn[j]+1], dep[x[fa]])
    

    阶段3:连接原图边

    出边连接

    for 每条从u出发的边e = (u,v,c,y):
        link(id[Find(y)], e, c)
    

    入边连接

    for 每条到达u的边e = (u,v,c,y):
        link(e, suf[1], dep[y])  // 直接连接到后缀链
        if (Find(y) ≠ 1) link(e, id[Find(y)], 0)
    

    阶段4:运行最短路算法

    // 初始化
    for 每条从节点1出发的边e:
        dis[e] = c[e]
        push e to priority_queue
    
    // Dijkstra
    while queue not empty:
        pop min node u
        if visited[u]: continue
        mark u visited
        for each edge (u, v, w):
            if dis[v] > dis[u] + w:
                dis[v] = dis[u] + w
                push v to queue
    

    阶段5:统计答案

    for 每条边e = (u,v,c,y):
        ans[v] = min(ans[v], dis[e])
    

    复杂度分析

    时间复杂度

    • 虚树构建O(klogk)O(k \log k) 每个节点,总共 O(nklogk)O(nk \log k)
    • 最短路O((m+nk)log(m+nk))O((m + nk) \log (m + nk))
    • 总体O((m+nk)log(m+nk))O((m + nk) \log (m + nk))

    空间复杂度

    • 图存储O(m+nk)O(m + nk)
    • 虚树相关O(k)O(k) 每个节点

    正确性证明

    1. 状态完整性

    通过虚树,我们包含了所有可能的口令转移:

    • 直接转移:口令不变或微小变化
    • 间接转移:通过前后缀链实现口令大跳转

    2. 代价正确性

    前后缀链保证了:

    • 对于任意目标口令 yy,我们都能找到最优的源口令 xx
    • LCP代价通过LCA深度正确计算

    3. 最优性

    Dijkstra算法在扩展后的图上保证找到全局最优解。

    优化技巧总结

    1. 虚树压缩:将指数级状态压缩到线性
    2. 前后缀链O(1)O(1) 时间查询最小代价
    3. 统一编号:所有节点(原图边、虚树节点、辅助节点)统一编号,便于Dijkstra
    4. 链式前向星:高效存储稀疏图

    边界情况处理

    1. 空口令:对应字典树根节点1
    2. 自环重边:题目明确说明可能存在
    3. 内存管理:多组数据要及时清空
    4. 整数溢出:使用long long存储距离

    这种方法将复杂的动态口令问题转化为经典的最短路问题,通过巧妙的图构建避免了状态爆炸,是图论建模的典范。

    • 1

    信息

    ID
    3124
    时间
    1500ms
    内存
    256MiB
    难度
    10
    标签
    递交数
    3
    已通过
    1
    上传者