「HNOI2015」接水果 - 题解

「HNOI2015」接水果

题意:

给你一个树上路径集合$ S $ ,每条路径有个权值。每次询问一条路径 $ (x, y) $ ,问它在 $ S $ 中包含的路径中权值第 $ k $ 小的是多少。

做法:

首先考虑如何判断判断一条路径是否被另一条路径包含。
当一条路径 $ (x, y) (dep_x < dep_y) $ 是一条深度从浅到深的链时,路径 $ (u, v) (dep_u < dep_v) $ 包含路径 $ (x, y) $ 满足 $ v $ 在 $ y $ 的子树内、 $ u $ 在 $ x $ 的孩子、 $ y $ 的祖先 的子树外, $ u, v $ 在树的dfn序上为连续几段。
当一条路径 $ (x, y) (dep_x < dep_y) $ 不是一条深度从浅到深的链时,路径 $ (u, v) $ 包含路径 $ (x, y) $ 满足 $ v $ 在 $ y $ 的子树内、 $ u $ 在 $ x $ 的祖先 的子树外, $ u, v $ 在树的dfn序上为连续几段。
然后树套树维护路径 $ (x, y) $ 包含多少路径,树套树维护即可。

对于每一个询问,我们都可以二分答案。将集合 $ S $ 中的路径按权值排序,然后查询其中包含的排名即可。我们发现这可以整体二分。时间复杂度 $ O(n \log^3{n}) $

在Luogu上开O2快如风,在LOJ上被1s时限卡自闭。。。

其实树套树可以用扫描线代替的,时间复杂度 $ O(n \log^2{n}) $。

#include <bits/stdc++.h>
#define pb push_back
#define LNK(i, a) for(int i = hed[a]; i; i = nxt[i])
using namespace std;
const int N = 40010;
int n, m, q;
int cnt = 0, hed[N], to[N + N], nxt[N + N];
int dfn[N], low[N], idx = 0, fa[16][N], dep[N];
struct Plate {
    int x, y, w;
    inline bool operator<(const Plate &yy)const { return w < yy.w; }
}; Plate a[N];
struct Fruit {
    int x, y, k, id;
    inline bool operator<(const Fruit &yy)const { return x < yy.x; }
};
int pp[N], lp = 0, ans[N];

namespace Tree {
    struct Line {
        int x, y, w;
        Line(int X = 0, int Y = 0, int W = 0) : x(X), y(Y), w(W) {}
        inline bool operator<(const Line &yy)const { return x < yy.x; }
    }; Line t[N * 20]; int tot = 0;
    int tr[N];
    
    inline void add(int x, int w) { for(; x <= n; x += x & -x) tr[x] += w; }
    inline int ask(int x) {
        int ss = 0; for(; x; x -= x & -x) ss += tr[x]; return ss;
    }
    inline void mdy(int x, int y, int xx, int yy, int w) {
        t[++tot] = Line(x, xx, w), t[++tot] = Line(x, yy + 1, -w);
        t[++tot] = Line(y + 1, xx, -w), t[++tot] = Line(y + 1, yy + 1, w);
    }
}

void gi(int &x) {
    x = 0; register char c = getchar();
    for(; c < '0' || c > '9'; c = getchar());
    for(; c >= '0' && c <= '9';)
        x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
}
inline void add(int x, int y) {
    to[++cnt] = y, nxt[cnt] = hed[x], hed[x] = cnt;
}
void dfs(int u, int ff) {
    fa[0][u] = ff, dep[u] = dep[ff] + 1, dfn[u] = ++idx;
    for(int i = 1; i < 16; ++i) fa[i][u] = fa[i - 1][fa[i - 1][u]];
    LNK(i, u) if(to[i] ^ ff) dfs(to[i], u); low[u] = idx;
}
int LCA(int x, int y) {
    if(dep[x] < dep[y]) swap(x, y);
    for(int i = 15; ~i; --i) if(dep[fa[i][x]] >= dep[y]) x = fa[i][x];
    return x == y;
}
int find(int x, int y) {
    for(int i = 15; ~i; --i) if(dep[fa[i][y]] > dep[x]) y = fa[i][y];
    return y;
}
void solve(vector<Fruit> Q, int l, int r, int L, int R) {
    if(l >= r) {
        for(int i = 0; i < Q.size(); ++i) ans[Q[i].id] = pp[l]; return ;
    }
    int mid = (l + r) >> 1, pos; vector<Fruit> ls, rs;
    for(int i = L, lca, x, y; i <= R; ++i) {
        if(a[i].w > pp[mid]) break;
        pos = i, x = a[i].x, y = a[i].y, lca = LCA(x, y);
        if(lca) {
            x = find(x, y);
            Tree::mdy(dfn[y], low[y], 1, dfn[x] - 1, 1);
            Tree::mdy(dfn[y], low[y], low[x] + 1, n, 1);
            Tree::mdy(1, dfn[x] - 1, dfn[y], low[y], 1);
            Tree::mdy(low[x] + 1, n, dfn[y], low[y], 1);
        }
        else {
            Tree::mdy(dfn[x], low[x], dfn[y], low[y], 1);
            Tree::mdy(dfn[y], low[y], dfn[x], low[x], 1);
        }
    }
    sort(Tree::t + 1, Tree::t + Tree::tot + 1); int j = 1;
    for(int i = 0, K; i < Q.size(); ++i) {
        for(; j <= Tree::tot && Tree::t[j].x <= Q[i].x; ++j)
            Tree::add(Tree::t[j].y, Tree::t[j].w);
        K = Tree::ask(Q[i].y);
        K >= Q[i].k ? ls.pb(Q[i]) : (Q[i].k -= K, rs.pb(Q[i]));
    }
    for(; j <= Tree::tot; ++j) Tree::add(Tree::t[j].y, Tree::t[j].w);
    Tree::tot = 0;
    solve(ls, l, mid, L, pos), solve(rs, mid + 1, r, pos + 1, R);
}
int main() {
    gi(n), gi(m), gi(q);
    for(int i = 1, x, y; i < n; ++i) gi(x), gi(y), add(x, y), add(y, x);
    dfs(1, 0);
    for(int i = 1; i <= m; ++i) {
        gi(a[i].x), gi(a[i].y), gi(a[i].w);
        if(dep[a[i].x] > dep[a[i].y]) swap(a[i].x, a[i].y);
    }
    sort(a + 1, a + m + 1), pp[++lp] = a[1].w;
    for(int i = 2; i <= m; ++i) if(a[i].w ^ a[i - 1].w) pp[++lp] = a[i].w;
    vector<Fruit> Q;
    for(int i = 1, x, y, k; i <= q; ++i)
        gi(x), gi(y), gi(k), Q.pb((Fruit){dfn[x], dfn[y], k, i});
    sort(Q.begin(), Q.end());
    solve(Q, 1, lp, 1, m); for(int i = 1; i <= q; ++i) printf("%d\n", ans[i]);
    return 0;
}

转载于:https://www.cnblogs.com/daniel14311531/p/10626248.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值