「NewCoder1C」保护

Problem

n n 个点的树,以1为根,给m条路径。
每次询问给 v v k,求一个在满足u-v这条路径至少被 k k 条前面给出的路径完全包含的条件下距离根最近的那个点u。(输出 uv u − v 的距离)

Solution

由于查询路径只会是直上直下的,所以我们把每条路径拆成两段直上直下的。

对于一条路径 uv u − v dep[u]dep[v] d e p [ u ] ≤ d e p [ v ] ,它被路径 xy x − y dep[x]dep[y] d e p [ x ] ≤ d e p [ y ] )包含,当且仅当 y y v的子树内且 dep[x]dep[u] d e p [ x ] ≤ d e p [ u ] 。而在子树中可以用 dfs d f s 序来表示。

所以我们对于一条路径 xy x − y ,我们可以表示为 (dfn[y],dep[x]) ( d f n [ y ] , d e p [ x ] ) 这个点对。

这个二维的不带修改问题,我们可以使用主席树。外层 dfs d f s 序,内层 dep d e p
那么我们对于一个 uv u − v ,可以在 O(logn) O ( l o g n ) 的时间得出有多少路径完全包含它。

但是 200000 200000 是可以卡掉大常数的 O(nlog2n) O ( n l o g 2 n ) 的。

我们把这个问题转化为求第k小

对于一个 v v ,我们求出下端点在它子树内,且上端点深度第k小的点,这就是我们的答案 u u 。此时前1~ k k 名肯定都可以覆盖uv,并且这也是最靠近根的。

如果求出来的 u u 并不在v的上面,说明无解。

时间复杂度 O(nlogn) O ( n l o g n )

Code

#include <bits/stdc++.h>
using namespace std;
#define N 200010
inline char gc() {
    static char now[1<<16], *S, *T;
    if(S == T) {T = (S = now) + fread(now, 1, 1<<16, stdin); if(S == T) return EOF;}
    return *S++;
}
inline int read() {
    int x = 0, f = 1; char c = gc();
    while(c < '0' || c > '9') {if(c == '-') f = -1; c = gc();}
    while(c >= '0' && c <= '9') {x = (x << 3) + (x << 1) + c - 48; c = gc();}
    return x * f;
}
struct adj {int to, nxt;}e[N<<1];
int n, m, hd[N], cnt = 1;
inline void addedge(int x, int y) {
    e[++cnt] = (adj){y, hd[x]}; hd[x] = cnt;
}
int f[N][18], sz[N], dep[N], son[N], in[N], out[N], tim = 0;
void dfs1(int x) {
    in[x] = ++tim;
    for(int i = 1; i <= 17; ++i)
        f[x][i] = f[f[x][i - 1]][i - 1];
    sz[x] = 1;
    for(int i = hd[x]; i; i = e[i].nxt) {
        int y = e[i].to;
        if(y == f[x][0]) continue;
        dep[y] = dep[x] + 1;
        f[y][0] = x;
        dfs1(y);
        sz[x]+= sz[y];
        if(sz[y] > sz[son[x]]) son[x] = y;
    }
    out[x] = tim;
}
int tp[N];
void dfs2(int x, int Tp) {
    tp[x] = Tp;
    if(son[x]) dfs2(son[x], Tp);
    for(int i = hd[x]; i; i = e[i].nxt) {
        int y = e[i].to;
        if(y != f[x][0] && y != son[x])
            dfs2(y, y);
    }
}
inline int LCA(int x, int y) {
    while(tp[x] != tp[y]) {
        if(dep[tp[x]] < dep[tp[y]]) swap(x, y);
        x = f[tp[x]][0];
    }
    return (dep[x] < dep[y]) ? x : y;
}
struct item {int x, y;}soldier[N<<1];
inline bool operator < (const item &A, const item &B) {
    return in[A.x] < in[B.x];
}
int rt[N], L[N * 360], R[N * 360], v[N * 360], len = 0;
void add(int pre, int &p, int l, int r, int x) {
    p = ++len; v[p] = v[pre] + 1;
    if(l == r) return ;
    int mid = (l + r)>>1;
    if(x <= mid) R[p] = R[pre], add(L[pre], L[p], l, mid, x);
    else L[p] = L[pre], add(R[pre], R[p], mid + 1, r, x);
}
int query(int A, int B, int l, int r, int k) {
    if(l == r) return l;
    int mid = (l + r)>>1;
    if(v[L[A]] - v[L[B]] >= k) return query(L[A], L[B], l, mid, k);
    else return query(R[A], R[B], mid + 1, r, k - v[L[A]] + v[L[B]]);
}
int main() {
    n = read(); m = read();
    memset(hd, 0, sizeof(hd));
    for(int i = 1; i < n; ++i) {
        int x = read(), y = read();
        addedge(x, y);
        addedge(y, x);
    }
    dep[1] = 1; dfs1(1); dfs2(1, 1);
    for(int i = 1; i <= m; ++i) {
        int x = read(), y = read(), z = LCA(x, y);
        soldier[2 * i - 1] = (item){x, z};
        soldier[2 * i] = (item){y, z};
    }
    sort(soldier+1, soldier+2*m+1);
    for(int i = 1; i <= 2 * m; ++i) {
        for(int j = in[soldier[i - 1].x] + 1; j < in[soldier[i].x]; ++j) rt[j] = rt[in[soldier[i - 1].x]];
        add(rt[in[soldier[i - 1].x]], rt[in[soldier[i].x]], 1, n, dep[soldier[i].y]);
    }
    for(int i = in[soldier[2 * m].x] + 1; i <= n; ++i) rt[i] = rt[in[soldier[2 * m].x]];
    int q = read();
    for(int i = 1; i <= q; ++i) {
        int x = read(), k = read();
        if(v[rt[out[x]]] - v[rt[in[x] - 1]] < k) {puts("0"); continue;}
        int ans = query(rt[out[x]], rt[in[x] - 1], 1, n, k);
        printf("%d\n", max(0, dep[x] - ans));
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值