P1099 树网的核 && P2491 [SDOI2011]消防

时间:2021-02-16 18:43:13

给定一棵树, 你可以在树的直径上确定一条长度不超过 \(S\) 的链, 使得树上离此链最长的点距离最小, 输出这个距离
P2491 数据范围为 P1099 的 \(1000\)

Solution

首先两次 \(dfs\) 确定树的直径, 即第一次随意从某一点出发到达最远点记为 \(s\), 第二次从 \(s\) 出发到达最远点 \(t\) , 则 \(s-t\) 即为树的直径
现在我们得到了直径, 试想树上现在有一条链, 包含点 \(a_{1},a_{2}...a_{n}\), 树上到此链最长的点无非是以下三个的最大值:

  1. \(a_{1}\) 到直径首的长度
  2. \(a_{t}\) 到直径末的长度
  3. \(Max_{i = 1}^{t}d_{i}\)\(d_{i}\) 为点 \(i\) 在不经过直径能到达最远点的长度

通过树的性质可得: 链上最远点长度必然包括直径到最远点的长度(才能保证最优)
所以实际上我们只需要维护在直径上长度不超过 \(S\) 的链的 \(1, 2\) 两点, 与直径最长取最大值即可
贪心可得在保证链长 \(<=S\) 的情况下越长越优
所以维护两个指针 \(l, r\), 维护链上的左右端点
枚举每一个右端点同时保持链长 \(<= S\) ,缩短左端点更新答案即可

此题细节较多,看注释

Code(P2491)

#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
#include<algorithm>
#include<vector>
#define LL long long
using namespace std;
int RD(){
    int out = 0,flag = 1;char c = getchar();
    while(c < '0' || c >'9'){if(c == '-')flag = -1;c = getchar();}
    while(c >= '0' && c <= '9'){out = out * 10 + c - '0';c = getchar();}
    return flag * out;
    }
const int maxn = 600019,INF = 1e9 + 19;
int head[maxn],nume = 1;
struct Node{
    int v,dis,nxt;
    }E[maxn << 3];
void add(int u,int v,int dis){
    E[++nume].nxt = head[u];
    E[nume].v = v;
    E[nume].dis = dis;
    head[u] = nume;
    }
int num, S;
int s, t;
int pre[maxn], dis[maxn], disl[maxn], disr[maxn], disnxt[maxn];//前驱点,非路径距离,链上距离,邻接距离
vector<int>G;
bool vis[maxn];
struct Fa{int p, dis;};
Fa get_longest(int u, int F, int tim){//函数多用
    Fa ret = (Fa){u, 0};int last = u, dn = 0;
    for(int i = head[u];i;i = E[i].nxt){
        int v = E[i].v;
        if(v == F || vis[v])continue;
        Fa ansv = get_longest(v, u, tim);
        if(ansv.dis + E[i].dis > ret.dis){
            last = v, dn = E[i].dis;
            ret.dis = ansv.dis + E[i].dis;
            ret.p = ansv.p;
            }
        }
    if(tim == 1)pre[u] = last, disnxt[u] = dn;
    return ret;
    }
int ans = INF;
struct Que{
    int index, val;
    }Q[maxn];
int get_min(){
    int now = s, add = 0;
    while(pre[now] != now){
        G.push_back(now);//入链
        dis[now] = get_longest(now, -1, 0).dis;//处理非链上最长路径
        add = max(add, dis[now]);
        now = pre[now];
        }
    G.push_back(now);
    dis[now] = get_longest(now, -1, 0).dis;//处理终点
    add = max(add, dis[now]);
    int tot = 0;
    for(int i = G.size() - 1;i >= 1;i--){
        now = G[i];
        disr[now] = tot;
        tot += disnxt[G[i - 1]];
        }
    disr[s] = tot;//处理链上最长距离的另一半
    int l = 0, r = 0, len = 0;
    ans = max(add, disr[G[r]]);
    while(r < G.size()){
        len += disnxt[G[r]];r++;
        while(len > S)len -= disnxt[G[l]], l++;
        //printf("l=%d r=%d\n", G[l], G[r]);
        now = max(disr[G[r]], disl[G[l]]);
        now = max(now, add);
        ans = min(ans, now);
        }
    ans = max(ans, add);
    return ans;
    }
int main(){
    num = RD();S = RD();
    for(int i = 1;i <= num - 1;i++){
        int u = RD(), v = RD(), dis = RD();
        add(u, v, dis), add(v, u, dis);
        }
    s = get_longest(1, -1, 0).p, t = get_longest(s, -1, 1).p;
    int i = s, tot = 0;
    while(pre[i] != i)vis[i] = 1, disl[i] = tot, tot += disnxt[i], i = pre[i];//s到t
    vis[t] = 1, disl[t] = tot;//标记直径上的点, 处理链上最长路径
    printf("%d\n", get_min());
    return 0;
    //for(int i = 1;i <= num;i++)if(vis[i])printf("dis[%d]=%d\n", i, dis[i]);
    }