[BZOJ4009][HNOI2015]接水果(dfs序+扫描线+线段树套线段树)

Description

风见幽香非常喜欢玩一个叫做 osu!的游戏,其中她最喜欢玩的模式就是接水果。
由于她已经DT FC 了The big black, 她觉得这个游戏太简单了,于是发明了一个更
加难的版本。首先有一个地图,是一棵由 n 个顶点、n-1 条边组成的树(例如图 1
给出的树包含 8 个顶点、7 条边)。这颗树上有 P 个盘子,每个盘子实际上是一条
路径(例如图 1 中顶点 6 到顶点 8 的路径),并且每个盘子还有一个权值。第 i 个
盘子就是顶点a_i到顶点b_i的路径(由于是树,所以从a_i到b_i的路径是唯一的),
权值为c_i。接下来依次会有Q个水果掉下来,每个水果本质上也是一条路径,第
i 个水果是从顶点 u_i 到顶点v_i 的路径。幽香每次需要选择一个盘子去接当前的水
果:一个盘子能接住一个水果,当且仅当盘子的路径是水果的路径的子路径(例如
图1中从 3到7 的路径是从1到8的路径的子路径)。这里规定:从a 到b的路径与
从b到 a的路径是同一条路径。当然为了提高难度,对于第 i 个水果,你需要选择
能接住它的所有盘子中,权值第 k_i 小的那个盘子,每个盘子可重复使用(没有使用次数
的上限:一个盘子接完一个水果后,后面还可继续接其他水果,只要它是水
果路径的子路径)。幽香认为这个游戏很难,你能轻松解决给她看吗?

Input

第一行三个数 n和P 和Q,表示树的大小和盘子的个数和水果的个数。

接下来n-1 行,每行两个数 a、b,表示树上的a和b 之间有一条边。树中顶点
按1到 n标号。 接下来 P 行,每行三个数 a、b、c,表示路径为 a 到 b、权值为 c 的盘子,其
中0≤c≤10^9,a不等于b。
接下来Q行,每行三个数 u、v、k,表示路径为 u到 v的水果,其中 u不等于v,你需要选择第 k小的盘子,
第k 小一定存在。

Output

对于每个果子,输出一行表示选择的盘子的权值。

Sample Input

10 10 10

1 2

2 3

3 4

4 5

5 6

6 7

7 8

8 9

9 10

3 2 217394434

10 7 13022269

6 7 283254485

6 8 333042360

4 6 442139372

8 3 225045590

10 4 922205209

10 8 808296330

9 2 486331361

4 9 551176338

1 8 5

3 8 3

3 8 4

1 8 3

4 8 1

2 3 1

2 3 1

2 3 1

2 4 1

1 4 1

Sample Output

442139372

333042360

442139372

283254485

283254485

217394434

217394434

217394434

217394434

217394434

HINT

N,P,Q<=40000。

Solution

下面把树选一个点为根进行一遍 dfs , dfnu d f n u u u 的 dfs 序, sizeu u u 的子树大小。
考虑如何把「 a>b x>y x − > y 的子路径」这一条件转化。
如果 a a b 在树上没有祖先关系,那么设 dfna<dfnb d f n a < d f n b dfnx<dfny d f n x < d f n y ,原条件等价于:
a a 的子树内有 x ,且 b b 的子树内有 y 。即:

dfnx[dfna,dfna+sizea1] AND  d f n x ∈ [ d f n a , d f n a + s i z e a − 1 ]  AND 

dfny[dfnb,dfnb+sizeb1] d f n y ∈ [ d f n b , d f n b + s i z e b − 1 ]

再考虑如果 a a b 的祖先,这时候可以找出一个点 u u ,它满足:
u 的父亲是 a a u a>b a − > b 的路径上。
那么这样条件可以转化为:
(1) b b 的子树内有 x u u 的子树内没有 y
(2) b b 的子树内有 y u u 的子树内没有 x
(满足上面两个条件 之一即可)
也就是说:
(dfnx[dfnb,dfnb+sizeb1] AND  ( d f n x ∈ [ d f n b , d f n b + s i z e b − 1 ]  AND 

dfny[dfnu,dfnu+sizeu1]) OR  d f n y ∉ [ d f n u , d f n u + s i z e u − 1 ] )  OR 

(dfny[dfnb,dfnb+sizeb1] AND  ( d f n y ∈ [ d f n b , d f n b + s i z e b − 1 ]  AND 

dfnx[dfnu,dfnu+sizeu1]) d f n x ∉ [ d f n u , d f n u + s i z e u − 1 ] )

于是问题转化为,有一些带权值的矩形,每次询问包含一个点 (x,y) ( x , y ) 的所有矩形中,权值第 k k 小的矩形。
直接做是不可做的,因此考虑离线后使用扫描线解决,把一个矩形拆成进入扫描线和离开扫描线两次操作。
这样就变成了另一个问题:一个序列,每次在区间 [l,r] 内的每个位置插入或删除一个数 x x ,或者询问一个位置 x 上第 k k 小的数。
可以使用线段树套线段树维护这个序列,第一维值域,第二维位置。对于空间问题可以使用动态开点。
修改时,从外层线段树的根一直走到 x 表示的叶子节点,把每个节点内部线段树的区间 [l,r] [ l , r ] 加(减) 1 1 <script type="math/tex" id="MathJax-Element-10567">1</script> 。
询问时,在外层线段树上二分可以得到答案。

Code

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define p2 p << 1
#define p3 p << 1 | 1
#define For(i, a, b) for (i = a; i <= b; i++)
#define Edge(u) for (int e = adj[u], v; e; e = nxt[e])
using namespace std;
inline int read() {
    int res = 0; bool bo = 0; char c;
    while (((c = getchar()) < '0' || c > '9') && c != '-');
    if (c == '-') bo = 1; else res = c - 48;
    while ((c = getchar()) >= '0' && c <= '9')
        res = (res << 3) + (res << 1) + (c - 48);
    return bo ? ~res + 1 : res;
}
const int N = 4e4 + 5, M = N << 1, R = M << 1, Z = R << 1, L = 3e7 + 5, LogN = 18;
int n, p, q, m, ecnt, nxt[M], adj[N], go[M], dfn[N], sze[N],
times, fa[N][LogN], dep[N], tm, qp[Z], rt[R], QAQ, ans[N];
struct cyx {int i, l, r, c, op;} orz[Z];
struct pyz {int u, v, c, id;} otz[N];
struct mx {int lc, rc, add; void init() {lc = rc = add;}} T[L];
void modify(int l, int r, int s, int e, int v, int &p) {
    if (!p) T[p = ++QAQ].init(); if (l == s && r == e)
        return (void) (T[p].add += v); int mid = l + r >> 1; if (T[p].add) {
        if (!T[p].lc) T[T[p].lc = ++QAQ].init();
        if (!T[p].rc) T[T[p].rc = ++QAQ].init();
        T[T[p].lc].add += T[p].add; T[T[p].rc].add += T[p].add; T[p].add = 0;
    }
    if (e <= mid) modify(l, mid, s, e, v, T[p].lc);
    else if (s >= mid + 1) modify(mid + 1, r, s, e, v, T[p].rc);
    else modify(l, mid, s, mid, v, T[p].lc),
        modify(mid + 1, r, mid + 1, e, v, T[p].rc);
}
void change(int l, int r, int pos, int x, int y, int d, int p) {
    modify(1, n, x, y, d, rt[p]); if (l == r) return; int mid = l + r >> 1;
    if (pos <= mid) change(l, mid, pos, x, y, d, p2);
    else change(mid + 1, r, pos, x, y, d, p3);
}
int query(int l, int r, int pos, int p) {
    if (!p) return 0; if (l == r) return T[p].add; int mid = l + r >> 1;
    return T[p].add + (pos <= mid ? query(l, mid, pos, T[p].lc)
        : query(mid + 1, r, pos, T[p].rc));
}
int ask(int l, int r, int pos, int k, int p) {
    if (l == r) return l; int mid = l + r >> 1, delta = query(1, n, pos, rt[p2]);
    if (k <= delta) return ask(l, mid, pos, k, p2);
    else return ask(mid + 1, r, pos, k - delta, p3);
}
inline bool cyxdalao(const cyx &a, const cyx &b) {return a.i < b.i;}
inline bool pyzdalao(const pyz &a, const pyz &b) {return a.v < b.v;}
void add_edge(int u, int v) {
    nxt[++ecnt] = adj[u]; adj[u] = ecnt; go[ecnt] = v;
    nxt[++ecnt] = adj[v]; adj[v] = ecnt; go[ecnt] = u;
}
void dfs(int u, int fu) {
    int i; dfn[u] = ++times; dep[u] = dep[fa[u][0] = fu] + (sze[u] = 1);
    For (i, 0, 15) fa[u][i + 1] = fa[fa[u][i]][i];
    Edge(u) if ((v = go[e]) != fu) dfs(v, u), sze[u] += sze[v];
}
int jump(int u, int x) {
    int i; For (i, 0, 16) if ((x >> i) & 1) u = fa[u][i]; return u;
}
int main() {
    int i, x, y, z; n = read(); p = read(); q = read();
    For (i, 1, n - 1) x = read(), y = read(), add_edge(x, y); dfs(1, 0);
    For (i, 1, p) {
        x = read(); y = read(); z = read(); if (dfn[x] > dfn[y]) swap(x, y);
        if (dfn[x] <= dfn[y] && dfn[y] <= dfn[x] + sze[x] - 1) {
            int o = jump(y, dep[y] - dep[x] - 1), xl = dfn[o],
            xr = dfn[o] + sze[o] - 1; if (xl > 1) {
                orz[++m] = (cyx) {dfn[y], 1, xl - 1, z, 1};
                orz[++m] = (cyx) {dfn[y] + sze[y], 1, xl - 1, z, -1};
            }
            if (xr < n) {
                orz[++m] = (cyx) {dfn[y], xr + 1, n, z, 1};
                orz[++m] = (cyx) {dfn[y] + sze[y], xr + 1, n, z, -1};
            }
            orz[++m] = (cyx) {1, dfn[y], dfn[y] + sze[y] - 1, z, 1};
            orz[++m] = (cyx) {xl, dfn[y], dfn[y] + sze[y] - 1, z, -1};
            orz[++m] = (cyx) {xr + 1, dfn[y], dfn[y] + sze[y] - 1, z, 1};
            orz[++m] = (cyx) {n + 1, dfn[y], dfn[y] + sze[y] - 1, z, -1};
        }
        else {
            int xl = dfn[x], xr = dfn[x] + sze[x] - 1;
            orz[++m] = (cyx) {dfn[y], xl, xr, z, 1};
            orz[++m] = (cyx) {dfn[y] + sze[y], xl, xr, z, -1};
        }
    }
    For (i, 1, q) {
        x = read(); y = read(); z = read(); if (dfn[x] > dfn[y]) swap(x, y);
        otz[i] = (pyz) {dfn[x], dfn[y], z, i};
    }
    sort(orz + 1, orz + m + 1, cyxdalao); sort(otz + 1, otz + q + 1, pyzdalao);
    For (i, 1, m) qp[i] = orz[i].c; sort(qp + 1, qp + m + 1);
    tm = unique(qp + 1, qp + m + 1) - qp - 1; For (i, 1, m)
        orz[i].c = lower_bound(qp + 1, qp + tm + 1, orz[i].c) - qp;
    int j = 1; For (i, 1, q) {
        while (j <= m && orz[j].i <= otz[i].v)
            change(1, tm, orz[j].c, orz[j].l, orz[j].r, orz[j].op, 1), j++;
        ans[otz[i].id] = ask(1, tm, otz[i].u, otz[i].c, 1);
    }
    For (i, 1, q) printf("%d\n", qp[ans[i]]); return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值