[Luogu P3233] [BZOJ 3572] [HNOI2014]世界树

本文介绍了一种使用虚树和倍增LCA算法解决特定树形结构问题的方法。通过两次DFS和二分边界的技巧,解决了国王询问关于种族管理的问题,实现了高效查询。

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

洛谷传送门
BZOJ传送门

题目描述

世界树是一棵无比巨大的树,它伸出的枝干构成了整个世界。在这里,生存着各种各样的种族和生灵,他们共同信奉着绝对公正公平的女神艾莉森,在他们的信条里,公平是使世界树能够生生不息、持续运转的根本基石。

世界树的形态可以用一个数学模型来描述:世界树中有 n n 个种族,种族的编号分别从 1 n n ,分别生活在编号为 1 n n 的聚居地上,种族的编号与其聚居地的编号相同。有的聚居地之间有双向的道路相连,道路的长度为 1 。保证连接的方式会形成一棵树结构,即所有的聚居地之间可以互相到达,并且不会出现环。定义两个聚居地之间的距离为连接他们的道路的长度;例如,若聚居地 a a b 之间有道路, b b c 之间有道路,因为每条道路长度为 1 1 而且又不可能出现环,所以 a c c 之间的距离为 2

出于对公平的考虑,第 i i 年,世界树的国王需要授权 mi 个种族的聚居地为临时议事处。对于某个种族 x x x 为种族的编号),如果距离该种族最近的临时议事处为 y y y 为议事处所在聚居地的编号),则种族 x x 将接受 y 议事处的管辖(如果有多个临时议事处到该聚居地的距离一样,则 y y 为其中编号最小的临时议事处)。

现在国王想知道,在 q 年的时间里,每一年完成授权后,当年每个临时议事处将会管理多少个种族(议事处所在的聚居地也将接受该议事处管理)。 现在这个任务交给了以智慧著称的灵长类的你:程序猿。请帮国王完成这个任务吧。

输入输出格式

输入格式:

第一行为一个正整数 n n ,表示世界树中种族的个数。接下来n1行,每行两个正整数 x,y x , y ,表示 x x 聚居地与y聚居地之间有一条长度为 1 1 的双向道路。接下来一行为一个正整数q,表示国王询问的年数。接下来 q q 块,每块两行:第i块的第一行为 1 1 个正整数m[i],表示第 i i 年授权的临时议事处的个数。第i块的第二行为 m[i] m [ i ] 个正整数 h[l] h [ l ] h[2] h [ 2 ] 、…、 h[m[i]] h [ m [ i ] ] ,表示被授权为临时议事处的聚居地编号(保证互不相同)。

输出格式:

输出包含 q q 行,第i行为 m[i] m [ i ] 个整数,该行的第 j(j=12...m[i]) j ( j = 1 , 2... , , m [ i ] ) 个数表示第 i i 年被授权的聚居地h[j]的临时议事处管理的种族个数。

输入输出样例

输入样例#1:
10
2 1
3 2
4 3
5 4
6 1
7 3
8 3
9 4
10 1
5
2
6 1
5
2 7 3 6 9
1
8
4
8 7 10 3
5
2 9 3 5 8
输出样例#1:
1 9   
3 1 4 1 1   
10  
1 1 3 5   
4 1 3 1 1

说明

N300000,q300000,m[1]+m[2]+...+m[q]300000 N ≤ 300000 , q ≤ 300000 , m [ 1 ] + m [ 2 ] + . . . + m [ q ] ≤ 300000

解题分析

看到 m[i]300000 ∑ m [ i ] ≤ 300000 就知道又是一道虚树的题目。为了A这道题,蒟蒻还特地学了倍增 LCA L C A (之前都用的树剖 LCA L C A RMQ LCA R M Q   L C A QAQ)

考虑建出虚树如何DP。

首先, 对于每一个虚树上的节点,我们可以两次DFS求出虚树上所有点属于哪个点管辖, 再一遍DFS二分虚树上的边得到分界点, 就可以求出连通块的大小了。

在统计的时候有个小 trick t r i c k ,可以强制将1号点(深度最浅)的点插入虚树, 对于每个关键点找到高度最高的一个在虚树中的点, 统计时减掉边界点一下挂的子树即可。

每次查询后记得清零, 有些细节见代码。

#include <cstdio>
#include <cctype>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <limits.h>
#include <algorithm>
#define R register
#define IN inline
#define gc getchar()
#define W while
#define MX 300050
template <class T>
IN void in(T &x)
{
    x = 0; R char c = gc;
    W (!isdigit(c)) c = gc;
    W (isdigit(c))
    x = (x << 1) + (x << 3) + c - 48, c = gc;
}
int dot, q, cnt, cot, top, root, bcnt;
int ein[MX], eout[MX], head[MX], sta[MX], fat[20][MX], ans[MX],
dep[MX], siz[MX], ask[MX], tree[MX << 1], dis[MX], bl[MX], buf[MX];
bool vis[MX], inq[MX];//inq记录是否是关键点,vis记录是否在虚树里
struct Edge
{
    int to, len, nex;
}edge[MX << 1];
IN bool cmp(const int &x, const int &y)
{
    int key1 = x > 0 ? ein[x] : eout[-x];
    int key2 = y > 0 ? ein[y] : eout[-y];
    return key1 < key2;
}
IN void addedge(const int &from, const int &to, const int &len)
{
    edge[++cnt] = {to, len, head[from]};
    head[from] = cnt;
}
namespace LCA//倍增LCA
{
    void DFS(const int &now)
    {
        siz[now] = 1; ein[now] = ++cot;
        for (R int i = 1; i <= 19; ++i)
        {
            if(fat[i - 1][now]) fat[i][now] = fat[i - 1][fat[i - 1][now]];
            else break;
        }
        for (R int i = head[now]; i; i = edge[i].nex)
        {
            if(edge[i].to == fat[0][now]) continue;
            dep[edge[i].to] = dep[now] + 1;
            fat[0][edge[i].to] = now;
            DFS(edge[i].to);
            siz[now] += siz[edge[i].to];
        }
        eout[now] = ++cot;
    }
    IN int jump(R int now, R int step)
    {
        for (R int i = 0; i <= 19; ++i)
        {
            if((step >> i) & 1)
            {
                now = fat[i][now];
                step ^= 1 << i;
            }
            if(!step) break;
        }
        return now;
    }
    IN int query(R int x, R int y)
    {
        if(dep[x] < dep[y]) std::swap(x, y);
        x = jump(x, dep[x] - dep[y]);
        if(x == y) return x;
        for (R int i = 19; i >= 0; --i)
        {
            if(fat[i][x] != fat[i][y])
            x = fat[i][x], y = fat[i][y];
        }
        return fat[0][x];
    }
    IN int getdis(const int &x, const int &y)
    {return dep[x] + dep[y] - (dep[query(x, y)] << 1);}
}
namespace DP
{
    void DFS1(const int &now, const int &fa)//第一次DFS,统计子树对父节点的控制情况
    {
        if(inq[now]) bl[now] = now;
        int dmn = inq[now] ? 0 : INT_MAX; int dnxt;
        for (R int i = head[now]; i; i = edge[i].nex)
        {
            if(edge[i].to == fa) continue;
            DFS1(edge[i].to, now);
            dnxt = dep[bl[edge[i].to]] - dep[now];
            if(dnxt < dmn) bl[now] = bl[edge[i].to], dmn = dnxt;
            else if(dnxt == dmn && bl[edge[i].to] < bl[now])//注意距离相同时特判控制点大小
            bl[now] = bl[edge[i].to];
        }
        dis[now] = dmn;//记录到最近控制点的距离
    }
    void DFS2(const int &now, const int &fa)//第二次DFS,统计父节点对子树的控制情况
    {
        for (R int i = head[now]; i; i = edge[i].nex)
        {
            if(edge[i].to == fa) continue;
            if(dis[edge[i].to] > dis[now] - dep[now] + dep[edge[i].to])
            dis[edge[i].to] = dis[now] - dep[now] + dep[edge[i].to], bl[edge[i].to] = bl[now];
            else if(dis[edge[i].to] == dis[now] - dep[now] + dep[edge[i].to])
            if(bl[edge[i].to] > bl[now]) bl[edge[i].to] = bl[now];
            DFS2(edge[i].to, now);
        }
        ans[bl[now]] = std::max(ans[bl[now]], siz[now]);//找到最高点
    }
    void solve(const int &now, const int &fa)
    {
        int d, tar;
        for (R int i = head[now]; i; i = edge[i].nex)
        {
            if(edge[i].to == fa) continue;
            if(bl[edge[i].to] != bl[now])
            {
                d = LCA::getdis(bl[edge[i].to], bl[now]);
                if(d & 1) ans[bl[now]] -= siz[tar = LCA::jump(edge[i].to, d / 2 - dis[edge[i].to])];//中间有偶数个点,直接找到分界点减掉即可
                else
                {
                    tar = LCA::jump(edge[i].to, d / 2 - dis[edge[i].to]);
                    if(tar != now && tar != edge[i].to)//不在两个端点,可以调整
                    tar = LCA::jump(edge[i].to, d / 2 - dis[edge[i].to] - (bl[now] < bl[edge[i].to]));
                    else if(tar == now) tar =  LCA::jump(edge[i].to, d / 2 - dis[edge[i].to] - 1);//在上界,向下跳一格
                    ans[bl[now]] -= siz[tar];
                }
                if(edge[i].to != tar) ans[bl[edge[i].to]] += siz[tar] - siz[edge[i].to];
                //计算对子节点的贡献
            }
            solve(edge[i].to, now);
        }
    }
}
int main(void)
{
    int a, b, bd, lca, now, fa;
    in(dot);
    for (R int i = 1; i < dot; ++i)
    in(a), in(b), addedge(b, a, 0), addedge(a, b, 0);
    LCA::DFS(1); in(q);
    W (q--)
    {
        in(a); bd = a; top = cnt = 0; b = bcnt = a;
        for (R int i = 1; i <= a; ++i)
        in(tree[i]), ans[buf[i] = ask[i] = tree[i]] = 0, vis[tree[i]] = inq[tree[i]] = true, head[tree[i]] = 0;//各种清零
        std::sort(tree + 1, tree + 1 + a, cmp);
        for (R int i = 1; i < bd; ++i)
        {
            lca = LCA::query(tree[i], tree[i + 1]);
            if(!vis[lca]) vis[lca] = true, tree[++a] = buf[++bcnt] = lca, head[lca] = 0, ans[lca] = 0;
        }
        if(!vis[1]) vis[1] = true, tree[++a] = 1, buf[++bcnt] = 1, head[1] = 0; bd = a;
        for (R int i = 1; i <= bd; ++i)
        tree[++a] = -tree[i];
        std::sort(tree + 1, tree + 1 + a, cmp);
        for (R int i = 1; i <= a; ++i)
        {
            if(tree[i] > 0) sta[++top] = tree[i];
            else
            {
                now = sta[top--], fa = sta[top];
                if(top) addedge(now, fa, dep[now] - dep[fa]); addedge(fa, now, dep[now] - dep[fa]);
            }
        }
        DP::DFS1(1, 0); DP::DFS2(1, 0); DP::solve(1, 0);
        for (R int i = 1; i <= b; ++i)
        printf("%d ", ans[ask[i]]);
        for (R int i = 1; i <= bcnt; ++i) 
        vis[buf[i]] = false, inq[buf[i]] = false, ans[buf[i]] = 0;
        puts("");
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值