首页 > 其他分享 >[题解] [SDOI2011] 消防

[题解] [SDOI2011] 消防

时间:2024-10-03 17:33:44浏览次数:1  
标签:eo SDOI2011 消防 题解 路径 int 直径 ep nw

[题解] [SDOI2011] 消防

tag: 图论树的直径
题目链接(洛谷):https://www.luogu.com.cn/problem/P2491

题目描述

给定一棵 \(n\) 个节点的树,第 \(i\) 条边有边权 \(z_i\) 。要求找到树上一条长度不大于 \(s\) 的简单路径,使得不在路径上的点到该路径的最大距离最小。
数据范围:\(1 \leq n \leq 3 \times 10^5, 0 \leq z_i \leq 10^3\)

题解

不难得出结论:如果没有 \(s\) 的限制,则选择该树的直径最优。进一步考虑长度为 \(s\) 的限制对答案的影响,可以发现即使在限制长度的条件下,答案的路径依然是直径的子集。在此不做严格证明,可以尝试采用在直径上删边、在点上扩展边的方式验证。因此,我们枚举在直径中的路径区间来解决此问题。对于直径,可以使用两次 DFS 的方式求得直径上的所有点,及其中各相邻点之间的距离,这个过程的时间复杂度是 \(O(n)\)。对于区间枚举,我们使用双指针,从左到右枚举区间的左端点,利用 \(l \leq s\) 的条件确定右端点即可,这个过程的时间复杂度也是 \(O(n)\) 。

那么新的问题是,对于一条确定的直径上的路径,如何求得距离该路径最远的点的距离。我们按照将直径缩短的思路思考。最初始的情况,我们要考虑不在直径上的点到直径的距离,可以利用 DFS ,从每个直径上的点向不在直径上的点遍历,在 \(O(n)\) 的时间复杂度内求出每个不在直径上的点到直径的最大值,我们称之为初始最长路径,这些初始最长路径的最大长度就是该问题的初始答案。接下来考虑直径缩短,我们又可以得到一个新的结论:在路径从直径上缩短时,除初始最长路径之外,新的最长路径一定是缩短后的路径的两端到直径的两端的距离的较大值。这个结论也不难验证。因此,我们只需在双指针枚举时动态维护这个长度,同时更新直径上的答案即可。最终的答案是初始答案和直径上的较大值。

AC 代码

//
// Created by wxy3265 on 2024/10/3.
//
#include <iostream>
#include <cstdio>
#include <cstring>
#define int long long
using namespace std;

const int MAXN = 2e7 + 5e6;
const int MAXM = 2e7 + 5e6;

// 建图
int first[MAXN], v[MAXM], nt[MAXM], w[MAXM];
int te;
inline void addEdge(int x, int y, int ww) {
    nt[++te] = first[x];
    first[x] = te;
    v[te] = y;
    w[te] = ww;
}

// 第一次 DFS 找到直径的一端
int maxd, maxdi;
void dfs1(int x, int fa, int step) {
    if (step > maxd) maxd = step, maxdi = x;
    for (int eo = first[x]; eo; eo = nt[eo]) {
        const int ep = v[eo];
        if (ep == fa) continue;
        dfs1(ep, x, step + w[eo]);
    }
}

// 第二次 DFS 找到直径,同时求出直径上相邻点及距离(nxt、nw)
int f[MAXN], nxt[MAXN], nw[MAXN];
void dfs2(int x, int fa, int step) {
    for (int eo = first[x]; eo; eo = nt[eo]) {
        const int ep = v[eo];
        if (ep == fa) continue;
        dfs2(ep, x, step + w[eo]);
        if (f[ep] > f[x]) {
            f[x] = f[ep];
            nxt[x] = ep;
            nw[x] = w[eo];
        }
    }
    if (!f[x]) f[x] = step;
}

// 第三次 DFS 求出从各直径上的点出发不经过其他直径上的点的最远距离
bool id[MAXN];
int mx[MAXN];
void dfs3(int x, int fa) {
    for (int eo = first[x]; eo; eo = nt[eo]) {
        const int ep = v[eo];
        if (ep == fa || id[ep]) continue;
        dfs3(ep, x);
        mx[x] = max(mx[x], mx[ep] + w[eo]);
    }
}

int q[MAXN], head = 0, tail = 0;
signed main() {
    // 读入数据
    int n, s;
    cin >> n >> s;
    for (int i = 1; i <= n - 1; i++) {
        int x, y, ww;
        cin >> x >> y >> ww;
        addEdge(x, y, ww);
        addEdge(y, x, ww);
    }
    // 求直径
    dfs1(1, 0, 0);
    dfs2(maxdi, 0, 0);
    int tot = 0;
    head = 1;
    for (int i = maxdi; i != 0; i = nxt[i]) {
        q[++tail] = i;
        tot += nw[i];
        id[i] = true;
    }
    // 求初始答案
    int r = tail, now = tot, ans = 0, tmp = tot;
    for (int i = 1; i <= n; i++) {
        if (id[i]) {
            dfs3(i, 0);
            ans = max(ans, mx[i]);
        }
    }
    // 双指针遍历
    int dr = 0, dl = 0;
    while (head <= s) {
        while (r >= head && now > s) {
            r--;
            now -= nw[q[r]];
            dr += nw[q[r]];
        }
        tmp = min(tmp, max(dr, dl));
        dl += nw[q[head]];
        now -= nw[q[head]];
        head++;
        while (r <= tail && now + nw[q[r]] <= s) {
            now += nw[q[r]];
            dr -= nw[q[r]];
            r++;
        }
    }
    cout << max(ans, tmp) << '\n';
    return 0;
}

标签:eo,SDOI2011,消防,题解,路径,int,直径,ep,nw
From: https://www.cnblogs.com/wxy3265/p/18445835

相关文章

  • 算法基础课——基础算法题解
    排序算法(分治)快速排序:题面:给定你一个长度为\(n\)的整数数列。请你使用快速排序对这个数列按照从小到大进行排序。并将排好序的数列按顺序输出。输入格式输入共两行,第一行包含整数\(n\)。第二行包含\(n\)个整数(所有整数均在\(1\sim10^9\)范围内),表示整个数列。输......
  • 题解9.29-10.3
    1.MakeitAlternating如果它已经是交替的序列我们就不用管了,最终的目的是把序列变成交替的序列,那么我们可以把连续相同的数全部取出来只留下一个,可以分成几段相同的数,最后的结果就是把这些相同的数全部只保留一个,用排列组合C(m,1);第一个结果很简单,把重复的数加一下即可,后面的答......
  • CF1051F题解
    传送门:https://codeforces.com/problemset/problem/1051/F注意到\(m-n\le20\),求一个连通图中任意两点间最短路,我们不难想到将问题转换到树上。先求出树的任意一颗生成树,此时倍增或者树刨能轻松算出仅含树边的最小路径。而对于非树边,从边的角度显然很难做到,我们不妨从点的角度思......
  • 题解:CF724E Goods transportation
    可以在cnblog中阅读。题意有\(n\)座城市,第\(i\)座城市生产了\(p_i\)件货物,最多可以出售\(s_i\)件货物,编号小的城市可以向编号大的城市运输至多\(c\)件货物,问最多能出售多少货物。\(n\le10^4\)。分析乍一看是一个网络流问题,可以这样建图,令\(S\)为源点\(T\)......
  • ABC221G Jumping Sequences 题解
    JumpingSequences把移动的上下左右改成左上、左下、右上、右下(坐标轴旋转\(45\)°)。则最终目的地是\((A+B,A-B)\)。(以前移动的方式是\((\pmd_i,0),(0,\pmd_i)\)。现在每次移动的方式是\((\pmd_i,\pmd_i)\))则\(x,y\)两维可以分开考虑。目标:从\(d_1\simd_n\)中选......
  • Codeforces Round 976 (Div. 2) 题解
    CodeforcesRound976(Div.2)题解2020B一个常见的想法:开关灯=异或,虽然在这道题里没啥用注意到,第i盏灯的按钮被按的次数就是i的除它本身以外的因子个数而完全平方数的因子数为奇数,其他数的因子数为偶数点击查看更多信息#include<bits/stdc++.h>usingnamespacestd;voi......
  • 题解:CF1976D Invertible Bracket Sequences
    可以在cnblog中阅读。题意给一个合法括号序列,问有多少区间\([l,r]\),使得将区间内的每个括号翻转后,括号序列仍合法。分析十分套路地,我们将(看成\(+1\),将)看成\(-1\),则一个括号序列合法的充要条件是转换后的序列满足:前缀和任意位置非负;最后一项为\(0\)。考虑翻转......
  • ZZJC新生训练赛第二场题解
    先给出比赛链接:https://ac.nowcoder.com/acm/contest/92036A小红打怪ShowCodeAclassPoint{//点类public:intx,y;Point(){}Point(intx,inty):x(x),y(y){}Pointoperator+(constPoint&P)const{returnPoint(x+P.x,y+P.y);......
  • 题解:TopCoder12316 ThreeColorability
    Vjudge可以出成《三色绘恋》背景。题意给一个格点数为\((n+1)\times(m+1)\)的网格,给格点染色,相邻的格点不能染成同样的颜色。每个格子有一条对角线的边,可选N形和Z形。现在有一个残缺的网格,存在一些格子的对角线连法不确定,构造一种字典序最小的方案使得至少存在一种染色......
  • 树上最值路径 题解
    题意简述给你一棵\(n\)个结点的树,编号为\(1\simn\),求有多少路径\(\operatorname{Path}(u\rightarrowv)\),满足\(u=\max\{x\midx\in\operatorname{Path}(u\rightarrowv)\}\),\(v=\min\{x\midx\in\operatorname{Path}(u\rightarrowv)\}\)。......