luogu4775[NOI2018]情报中心 虚树 线段树合并

[NOI2018]情报中心

题目传送门

分析

即zjoi线图之后又一道两百行代码的题。
其实luogu题解里面的那份标解已经足够清楚了,自己写一遍只是为了加深理解。

题目大意:给定一棵带边权的树,给定树上的多条链,每条链有费用,求有公共边的两条链并的边权和-费用的最大值。

这道题的一个核心思路是考虑交边的状态,不难发现如下两种情况:
luogu4775[NOI2018]情报中心 虚树 线段树合并_第1张图片
luogu4775[NOI2018]情报中心 虚树 线段树合并_第2张图片
没错我盗的图,略略略
对于没有公共Lca的链,他们的链交是一条直上直下的链
对于有公共Lca的链,他们的链交一条没有任何特殊性质的链
对于前者,其特殊性在于链交,对于后者,公共的Lca本身就是很特殊的性质。
分情况讨论这两种情况

Case1

luogu4775[NOI2018]情报中心 虚树 线段树合并_第3张图片
不难发现,我们可以将每条链都拆成直上直下的两条链。我们把一条链的边权值加到另一条链上,这样我们可以把问题直接转化成所有链都是直上直下的情况。

一般统计这类问题,都要找基准点。考虑将红点,也就是交点的下端作为基准点统计答案。
考虑枚举所有的红点。那么对于每个红点,答案就是
L e n 1 + L e n 2 − C 1 − C 2 − D R e d + m a x ( D G r e e n , D B l u e ) Len_1+Len_2-C_1-C_2-D_{Red}+max(D_{Green},D_{Blue}) Len1+Len2C1C2DRed+max(DGreen,DBlue)
其中 u , v u,v u,v是两条链的下端点, C 1 , C 2 C_1,C_2 C1,C2是花费, D x D_x Dx表示 x x x的带权深度。
对于某个红点, D R e d D_{Red} DRed是常数。
考虑对于红点的每个子树开一棵线段树,每条链以上端点深度为下标,考虑启发是合并。合并的时候,考虑某个线段树节点的左右子树,右子树的 D D D肯定大于左子树的 D D D,那么答案就是 M a x ( L e n 1 + C 1 + D 1 ) + M a x ( L e n 2 + C 2 ) − D R e d Max(Len_1+C_1+D_1)+Max(Len_2+C_2)-D_{Red} Max(Len1+C1+D1)+Max(Len2+C2)DRed
线段树中维护 f x , 0 / 1 = L e n x + C x ( + D x ) f_{x,0/1}=Len_x+C_x(+D_x) fx,0/1=Lenx+Cx(+Dx)的最大值即可。

Case2

luogu4775[NOI2018]情报中心 虚树 线段树合并_第4张图片
稍微麻烦了一些。我们先考虑所有Lca都在同一个点上的情况。
我们仍然考虑将两个蓝点 u , v u,v u,v的答案统计在红点 R e d Red Red上。
2 A n s = L e n 1 + L e n 2 + D u + D v − 2 D R e d + D i s t ( p u , p v ) 2Ans=Len_1+Len_2+D_u+D_v-2D_{Red}+Dist(p_u,p_v) 2Ans=Len1+Len2+Du+Dv2DRed+Dist(pu,pv)
其中 D i s t ( p u , p v ) Dist(p_u,p_v) Dist(pu,pv)表示两个蓝点对应绿点的带权距离。
一个非常神仙的做法是,新建两个节点 p u ′ , p v ′ p'_u,p'_v pu,pv p u , p v p_u,p_v pu,pv连边,边权就是 L e n 1 + D u Len_1+D_u Len1+Du L e n 2 + D v Len_2+D_v Len2+Dv
就是求 D i s t ( p u ′ , p v ′ ) − 2 D R e d Dist(p'_u,p'_v)-2D_{Red} Dist(pu,pv)2DRed的最大值。
这是一个最远点对问题。
我们考虑维护红点的不同子树内蓝点对应的绿点集合的最远点对。
将所有子树合并起来即可得到红点的最远点对。合并的时候统计答案。
因为将两个最远点对集合合并可以 O ( 6 ) O(6) O(6)两两匹配比较做。
L c a Lca Lca不在同一个点上的时候,暴力记录一下蓝点所在的位置,对每个点跑个虚树即可。
总复杂度 O ( n l o g n ) O(nlogn) O(nlogn)
本题的破题关键在于:
1.信息的形式化表达。
采用红点作为基准点用尽量少的变量转化原问题。
2.变量的分离处理和维护。
将不同的变量的信息分离并维护,把复杂度从 O ( n 2 ) O(n^2) O(n2)降到 O ( n l o g ) O(nlog) O(nlog)

代码

当然还考察了选手的代码功底和数据结构功底啦!

#include
const int N = 1e5 + 10;
typedef long long LL;
const LL oo = 1e17;
LL ri() {
    char c = getchar(); LL x = 0, f = 1; for(;c < '0' || c > '9'; c = getchar()) if(c == '-') f  = -1;
    for(;c >= '0' && c <= '9'; c = getchar()) x = (x << 1) + (x << 3) - '0' + c; return x * f;
}
template<typename T>void cmax(T &a, T b) {a = std::max(a, b);}
int d[N], ps[N], Lg[N], de[N], sz[N], ds[N], fa[N], pr[N], nx[N], to[N], bin[20], mn[20][N], tot, tp, n;
LL D[N], ans;
void add(int u, int v) {to[++tp] = v; nx[tp] = pr[u]; pr[u] = tp;}
int Be(int u, int v) {return de[u] < de[v] ? u : v;}
int Lca(int u, int v) {
    u = ps[u]; v = ps[v];
    if(u > v)
        std::swap(u, v);
    int t = Lg[v - u + 1];
    return Be(mn[t][u], mn[t][v - bin[t] + 1]); 
}
LL Dis(int u, int v) {
    if(!u || !v) return -oo;
    return D[u] + D[v] - (D[Lca(u, v)] << 1);
}
void Dfs(int u) {
    de[u] = de[fa[u]] + 1; D[u] += D[fa[u]];
    mn[0][++tot] = u; ps[u] = tot; sz[u] = 1; ds[u] = 0;
    for(int i = pr[u]; i; i = nx[i]) {
        Dfs(to[i]); mn[0][++tot] = u;
        sz[u] += sz[to[i]];
        if(sz[to[i]] > sz[ds[u]]) 
            ds[u] = to[i];
    }
}
void Pre() {
    Dfs(1); bin[0] = 1;
    for(int i = 1;(bin[i] = bin[i - 1] << 1) <= tot; ++i) 
        for(int j = 1;j + bin[i] - 1 <= tot; ++j)
            mn[i][j] = Be(mn[i - 1][j], mn[i - 1][j + bin[i - 1]]);
    Lg[0] = -1;
    for(int i = 1;i <= tot; ++i)
        Lg[i] = Lg[i >> 1] + 1;
    d[1] = 1;
    for(int i = 2;i <= n; ++i)
        d[i] = ds[fa[i]] == i ? d[fa[i]] : i;
}
int Jump(int u, int c) {
    for(;d[u] != d[c]; u = fa[d[u]]) 
        if(fa[d[u]] == c)
            return d[u];
    return ds[c];
} 
namespace Diff {
    const int T = 2e6 + 10;
    LL mx0[T], mx1[T], w0, w1;
    int rt[N],  ls[T], rs[T], sz, A; 
    int Newnode() {
        ++sz;
        mx0[sz] = mx1[sz] = -oo; 
        ls[sz] = rs[sz] = 0;
        return sz;
    }
    void Ins(int &p, int L, int R, int x) {
        if(!p) p = Newnode();
        cmax(mx0[p], w0); cmax(mx1[p], w1);
        if(L == R) return ; int m = L + R >> 1;
        x <= m ? Ins(ls[p], L, m, x) : Ins(rs[p], m + 1, R, x);
        cmax(ans, mx0[ls[p]] + mx1[rs[p]] - D[A]);
    }
    void Up(int p) {
        mx0[p] = std::max(mx0[ls[p]], mx0[rs[p]]);
        mx1[p] = std::max(mx1[ls[p]], mx1[rs[p]]);
    }
    void Del(int &p, int L, int R, int x) {
        if(L == x) return p = 0, void();
        int m = L + R >> 1;
        x <= m ? rs[p] = 0, Del(ls[p], L, m, x) : Del(rs[p], m + 1, R, x);
        Up(p);
    }
    int Merge(int u, int v, int L, int R) {
        if(!u || !v) return u | v;
        if(L == R) {
            cmax(mx0[u], mx0[v]);
            cmax(mx1[u], mx1[v]);
            return u;
        }
        cmax(ans, mx0[ls[u]] + mx1[rs[v]] - D[A]);
        cmax(ans, mx0[ls[v]] + mx1[rs[u]] - D[A]);
        int m = L + R >> 1;
        ls[u] = Merge(ls[u], ls[v], L, m);
        rs[u] = Merge(rs[u], rs[v], m + 1, R);
        return Up(u), u; 
    }
    void Ins(int u, int c, LL w) {
        w0 = w; w1 = w + D[c]; A = u;
        Ins(rt[u], 1, n, de[c]);
    }
    void Restore() {
        sz = 0;
        for(int i = 1;i <= n; ++i)
            rt[i] = 0;
    }
    void Work() {
        for(int i = n; i > 2; --i) {
            Del(rt[i], 1, n, de[i] - 1); A = fa[i]; 
            rt[fa[i]] = Merge(rt[fa[i]], rt[i], 1, n);
        }
        Restore();
    }
}
namespace Same {
    struct Node {
        int u; LL w;
        Node(int _u = 0, LL _w = 0) : u(_u), w(_w) {}
    };
    LL Dis(Node u, Node v) {return ::Dis(u.u, v.u) + u.w + v.w;}
    struct Data {
        int u; Node v;
        Data(int _u = 0, Node _v = Node(0, 0)) : u(_u), v(_v) {}
    };
    std::vector<Data>vec[N];
    bool cmp(Data a, Data b) {return ps[a.u] < ps[b.u];}
    struct MaxDis {
        Node u, v;
        MaxDis(Node _u = Node(), Node _v = Node()) : u(_u), v(_v) {}
        bool operator < (MaxDis a) const {return Dis(u, v) < Dis(a.u, a.v);}
        void Union(MaxDis &a, int c) {
            if(!u.u)
                return *this = a, a = MaxDis(), void();
            MaxDis x(u, a.u);
            cmax(x, MaxDis(u, a.v));
            cmax(x, MaxDis(v, a.u));
            cmax(x, MaxDis(v, a.v));
            cmax(ans, (Dis(x.u, x.v) >> 1) - D[c]);
            cmax(x, a); cmax(*this, x);
            a = MaxDis();
        }
    }x[N];
    int h[N];
    void Ins(int x, int u, int v, LL w) {vec[x].push_back(Data(u, Node(v, w)));}
    void Up(int f, int u) {x[f].Union(x[u], f);}
    void Ins(int u) {
        if(h[tp] == u)
            return ;
        int c = Lca(h[tp], u);
        for(;tp > 1 && de[h[tp - 1]] >= de[c]; --tp)
            Up(h[tp - 1], h[tp]);
        if(h[tp] != c)
            Up(c, h[tp]), h[tp] = c;
        h[++tp] = u;
    }
    void Work() {
        for(int i = 2;i <= n; ++i) 
        if(vec[i].size()) {
            std::sort(vec[i].begin(), vec[i].end(), cmp);
            h[tp = 1] = i;
            for(Data j : vec[i])  {
                MaxDis Ad(j.v);
                x[j.u].Union(Ad, j.u);
                Ins(j.u);
            }
            for(;tp > 1; --tp)
                Up(h[tp - 1], h[tp]);
            if(tp)
                x[h[1]] = MaxDis();
            vec[i].clear();
        }
    }
}
int main() {
    Diff::mx0[0] = Diff::mx1[0] = -oo;
    for(int T = ri();T--;) {
        n = ri(); ans = -oo;
        for(int i = 1;i < n; ++i) {
            int u = ri(), v = ri(), w = ri();
            add(u, v); D[v] = w; fa[v] = u;
        }
        Pre();
        for(int m = ri(); m--;) {
            int u = ri(), v = ri(); LL w = ri();
            int c = Lca(u, v); LL len = Dis(u, v);
            if(u != c) {
                Diff::Ins(u, c, len - w);
                Same::Ins(Jump(u, c), u, v, len + D[u] - (w << 1));
            }
            if(v != c) {
                Diff::Ins(v, c, len - w);
                Same::Ins(Jump(v, c), v, u, len + D[v] - (w << 1));
            }
        }
        Diff::Work();
        Same::Work();
        if(ans < -1e15)
            puts("F");
        else
            printf("%lld\n", ans);
        tot = tp = 0;
        for(int i = 1;i <= n; ++i)
            pr[i] = 0;
    }
    return 0;
}

你可能感兴趣的:(数据结构-线段树&&树状数组,树上操作-虚树)