luogu2491 消防 题解

实际上就是 树网的核 的数据加强版。

原题暴力枚举即可,本题也可以用复杂度为 \(O(n\log SUM)\) 的二分答案,这里只讲述单调队列的 \(O(n)\) 算法。

题意:在树的直径上选择两个距离不超过 \(s\) 的点,最小化「偏心距」。

「偏心距」:树中距离直径最远的节点到直径的距离。

显然,可以用单调队列维护。

设直径为 \(u\),其节点数为 \(o\),直径上两点为 \((u_i,u_j)\)\(f(x)\) 为从 \(x\) 出发,不经过直径上的其他点,能够到达的最远距离。\(dis(x,y)\)\((x,y)\) 之间的距离。

不难想到此时偏心距为 \[ \max_{dis(i,j)\in [1,s]} {\{\max_{i \le k \le j} { \{ f(k) \},dis(u_1,u_i),dis(u_j,u_s) \} } } \] 仔细观察不难发现,\(f(k)\) 的定义域为 \([1,s]\),而无论如何选择两个点,最终一定取到 \(\max_{k \in [1,s]} { \{f(s)\} }\)

所以上式可化为 \[ \max_{dis(i,j) \in [1,s]} { \{ \max_{k \in [1,s]}{\{ f(k) \}},dis(u_1,u_i),dis(u_j,u_o) \} } \]\(\max_{k \in [1,s]} { \{f(s)\} }\) 是一个定值,设其为 \(K\)

求出 \(u\) 上节点距离的前缀和数组 \(S\)

\[ \max_{dis(i,j) \in [1,s]}{ \{ K, \max ( S(i),S(o)-S(j) ) \} } \] 用单调队列维护队头为 满足 \(S(j)-S(i) \le s\) 的最大的 \(j\) 即可。

写的很麻烦……

#include<bits/stdc++.h>
using namespace std;
const int N=3e5+10;
int n, s, t, mx, o, d[N], f[N], sum[N], a[N], b[N];
int c, h[N], ver[N<<1], w[N<<1], nxt[N<<1];
bool v[N];
void add(int x,int y,int z) { ver[++c]=y, w[c]=z, nxt[c]=h[x], h[x]=c; }
void DFS(int x,int fa) {
    // 这个是用来找直径的
	f[x]=fa;
    for(int i=h[x];i;i=nxt[i]) if(ver[i]!=fa) {
        int y=ver[i], z=w[i];
		d[y]=d[x]+z;
        if(d[y]>mx) mx=d[y], o=y;
        DFS(y,x);
    }
}
void dfs(int x) {
    v[x]=1;
    for(int i=h[x];i;i=nxt[i]) if(!v[ver[i]]) {
        // 不经过直径上的点
        int y=ver[i], z=w[i];
        dfs(y);
        f[x]=max(f[x],f[y]+z);
    }
    // 找距离
}
void ddfs(int x,int fa) {
	for(int i=h[x];i;i=nxt[i]) {
		int y=ver[i], z=w[i];
		if(v[y]&&y!=fa) a[++t]=x, b[t]=z, ddfs(y,x);
	}
    // a[]存直径节点编号,b[]存边权
    // 这种做法会漏掉直径最后一个点,不过不影响
}
int main() {
    int k=0, ans=1<<30;
    scanf("%d%d",&n,&s);
    for(int i=1;i<n;++i) {
        int x, y, z; scanf("%d%d%d",&x,&y,&z);
        add(x,y,z), add(y,x,z);
    }
    int p, q;
    DFS(1,0), p=o, mx=d[o]=0;
    DFS(p,0), q=o;
    // 这时候f[x]表示x的父亲
    while(p!=q) v[q]=1, q=f[q];
    // 标记直径上的点
    ddfs(q,0);
    // 累加直径上的边
    memset(f,0,sizeof(f));
    for(int i=1;i<=t;++i) {
        dfs(a[i]);
        // 这时候f[]表示能到达的最远距离
        k=max(k,f[a[i]]);
        sum[i]=sum[i-1]+b[i];
    }
    int l, r, qq;
    // qq记录到直径最远的距离
    for(l=1,r=1;l<=t;++l) {
        while(r<t&&sum[r+1]-sum[l]<=s) ++r;
        qq=max(k,max(sum[l],sum[t]-sum[r]));
        ans=min(ans,qq);
    }
    printf("%d\n",ans);
}

luogu2491 消防 题解
https://yozora0908.github.io/2021/lg2491-solution/
作者
yozora0908
发布于
2021年10月1日
许可协议