[Codechef October Challenge 2014]Union on Tree(虚树+点分树)

本文介绍了解决树上放置多个警卫问题的方法。利用点分树与虚树技术来计算警卫能够看守的节点数量,通过构建点分树和虚树,有效地解决了警卫覆盖范围的统计问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Description

一个树,边都是单位长度。

  • 你有QQ个询问,每个询问形如:

    • 假如在树上放m个警卫,第ii个警卫放在xi点,它可以看守到距离
      xixi点距离不超过riri的所有点。

    • 问这些警卫一起可以看到多少个点。
    • 每个询问都是独立的。

    • n50000n⩽50000,询问的警卫总数不超过 500000500000

    Solution

    考虑m=1的情况

    如果m=1m=1,则意味着只有一名警卫。那么询问就等价于查询距离一个节点小于rangerange的节点数。
    这就成为了一道点分树的板子题,在点分树上的每个节点维护A,BA,B两个vectorvector, 分别表示节点uu在点分树上的子树信息,与在原树上的子树信息。

    Au,i:在点分树中,以u为根的点分树子树距离点分树根节点(uu)不超过i的节点数。

    Bu,iBu,i:在原树中,以u为根的点分树子树距离此子树在原树中的根(faufau的对应儿子)节点(uu)不超过i的节点数。

    每次计算距离节点uu不超过r的节点数时的时候就暴力跳父节点,加上A[rd]A[r−d]减去对应子节点的B[rd1]B[r−d−1]

    考虑一般的询问

    m500000∑m⩽500000,很容易想到建虚树。
    现在考虑一般的询问如何计算,首先更新每个在虚树上的节点能警卫到的范围(虚树中添加的lca节点以及在某些情况下,rb<radist(a,b)rb<ra−dist(a,b)).
    更新后直接按前文所述累加每个点能警戒到的范围。

    但是这样有的节点被重复统计,考虑一条边(u,v)(u,v)zz是这条变的中点(即r[u]dist(u,z)=r[v]dist(v,z))。

    zz节点有一个性质,跨过z后用uuv优,否则用zz比用u优。

    所以这条边重复覆盖的部分就是uu跨过z警戒的范围,vv跨过z警戒的范围。即zz能警戒的范围。所以减去z能警戒的范围即可。

    #include <bits/stdc++.h>
    using namespace std;
    
    const int maxn = 100005;
    struct edge {
        int to, next;
    }e[maxn * 2];
    int h[maxn], tot, n, m, q, p[maxn], r[maxn], ans;
    
    inline void add(int u, int v)
    {
        e[++tot] = (edge) {v, h[u]}; h[u] = tot;
        e[++tot] = (edge) {u, h[v]}; h[v] = tot;
    }
    
    inline int gi()
    {
        char c = getchar();
        while(c < '0' || c > '9') c = getchar();
        int sum = 0;
        while('0' <= c && c <= '9') sum = sum * 10 + c - 48, c = getchar();
        return sum;
    }
    
    //树链剖分求lca
    int siz[maxn], dep[maxn], son[maxn], dfn[maxn], Time, fa[maxn], g[maxn], order[maxn];
    inline void dfs1(int u) //链剖dfs1
    {
        dep[u] = dep[fa[u]] + 1;
        siz[u] = 1;
        for(int i = h[u], v; i; i = e[i].next)
            if((v = e[i].to) != fa[u]) {
                fa[v] = u;
                dfs1(v); siz[u] += siz[v];
                if(siz[v] > siz[son[u]]) son[u] = v;
            }
    }
    
    inline void dfs2(int u) //链剖dfs2
    {
        order[dfn[u] = ++Time] = u;
        if(son[u]) {
            g[son[u]] = g[u]; dfs2(son[u]);
            for(int i = h[u], v; i; i = e[i].next)
                if((v = e[i].to) != fa[u] && v != son[u]) {
                    g[v] = v; dfs2(v);
                }
        }
    }
    
    inline int lca(int u, int v)
    {
        while(g[u] != g[v]) {
            if(dep[g[u]] > dep[g[v]]) u = fa[g[u]];
            else v = fa[g[v]];
        }
        return dep[u] < dep[v] ? u : v;
    }
    
    //构建点分树
    vector<int> A[maxn], B[maxn]; //A:点分树子树信息,B:原树子树信息
    int rt[maxn][20], d[maxn][20], len[maxn];
    
    int cent, Min, sum;
    bool vis[maxn];
    void getroot(int u, int fa) //找重心
    {
        int Mxsz = 0; siz[u] = 1;
        for(int i = h[u], v; i; i = e[i].next)
            if((v = e[i].to) != fa && !vis[v]) {
                getroot(v, u); siz[u] += siz[v];
                Mxsz = max(Mxsz, siz[v]);
            }
        Mxsz = max(Mxsz, sum - siz[u]);
        if(Mxsz < Min) Min = Mxsz, cent = u;
    }
    
    void dfs3(int u,int fa, int dis, int root, vector<int> &v) //统计子树信息
    {
        rt[u][len[u]] = root;
        d[u][len[u]++] = dis;
        if(dis == (int)v.size()) v.push_back(u <= n); else v[dis] += u <= n;
        for(int i = h[u]; i; i = e[i].next)
            if(e[i].to != fa && !vis[e[i].to])
                dfs3(e[i].to, u, dis + 1, root, v);
    }
    
    void dfs4(int u, int fa, int dis, vector<int> &v)
    {
        if(dis == (int)v.size()) v.push_back(u <= n); else v[dis] += u <= n;
        for(int i = h[u]; i; i = e[i].next)
            if(e[i].to != fa && !vis[e[i].to]) 
                dfs4(e[i].to, u, dis + 1, v);
    }
    
    void solve(int u, vector<int> &v) //构建点分树主过程
    {
        Min = n << 1; getroot(u, 0);
        vis[u = cent] = true;
        B[u] = v;
        dfs3(u, 0, 0, u, A[u]);
        for(int len = A[u].size(), i = 1; i < len; ++i) A[u][i] += A[u][i - 1];
        for(int i = h[u], v; i; i = e[i].next)
            if(!vis[v = e[i].to]) {
                vector<int> t(1, 0);
                dfs4(v, 0, 1, t);
                for(int len = t.size(), i = 1; i < len; ++i) t[i] += t[i - 1];
                sum = siz[v]; solve(v, t);
            } 
    }
    
    //建虚树
    int stk[maxn], top, pre[maxn];
    
    inline bool cmp1(const int &a, const int &b) {return dfn[a] > dfn[b];}
    inline bool cmp2(const int &a, const int &b) {return dfn[a] < dfn[b];}
    
    void build_tree()
    {
        sort(p + 1, p + m + 1, cmp1);
        stk[top = 1] = p[m]; 
        for(int i = m - 1, x; i >= 1; --i) {
            int u = lca(p[i], stk[top]);
            for(x = 0; dfn[stk[top]] > dfn[u]; x = stk[top--])
                if(x) pre[x] = stk[top];
            if(stk[top] != u) stk[++top] = p[++m] = u, r[u] = -1;
            if(x) pre[x] = stk[top];
            stk[++top] = p[i];
        }
        --top;
        while(top) pre[stk[top + 1]] = stk[top], --top;
        sort(p + 1, p + m + 1, cmp2);
    }
    
    inline int calc(int u, int dis) //计算距离u不超过dis的节点数
    {
        int s = 0;
        for(int i = 0; i < len[u]; ++i) {
            if(i && dis >= d[u][i - 1]) s -= B[rt[u][i]][min(dis - d[u][i - 1], (int)B[rt[u][i]].size() - 1)];
            if(dis >= d[u][i]) s += A[rt[u][i]][min(dis - d[u][i], (int)A[rt[u][i]].size() - 1)];
        }
        return s;
    }
    
    inline int jump(int u, int d)
    {
        while(dep[g[u]] > d) u = fa[g[u]];
        return order[dfn[u] - (dep[u] - d)];
    }
    
    int main()
    {
        freopen("tree.in", "r", stdin);
        freopen("tree.out", "w", stdout);
    
        n = gi();
        for(int i = 1; i < n; ++i) {
            add(gi(), n + i); add(gi(), n + i);
        }
        sum = 2 * n - 1;
    
        dfs1(1);
        Time = 0; g[1] = 1; dfs2(1);
        vector<int> v; solve(1, v);
    
        q = gi();
        for(int i = 1; i <= q; ++i) {
            m = gi();
            for(int i = 1; i <= m; ++i) p[i] = gi(), r[p[i]] = gi() << 1;
            build_tree();
            for(int i = m; i > 1; --i) 
                r[pre[p[i]]] = max(r[pre[p[i]]], r[p[i]] - (dep[p[i]] - dep[pre[p[i]]]));
            for(int i = 2; i <= m; ++i)
                r[p[i]] = max(r[p[i]], r[pre[p[i]]] - (dep[p[i]] - dep[pre[p[i]]]));
            ans = 0;
            for(int i = 1; i <= m; ++i) ans += calc(p[i], r[p[i]]);
            for(int i = 2, k; i <= m; ++i) {
                k = jump(p[i], (r[pre[p[i]]] - r[p[i]] + dep[p[i]] + dep[pre[p[i]]]) >> 1);
                List(k, r[p[i]] - (dep[p[i]] - dep[k]));
                ans -= calc(k, r[p[i]] - (dep[p[i]] - dep[k]));
            }
            printf("%d\n", ans);
        }
        return 0;
    }
    
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值