P3233 [HNOI2014]世界树

\(\color{#0066ff}{ 题目描述 }\)

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

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

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

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

\(\color{#0066ff}{输入格式}\)

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

\(\color{#0066ff}{输出格式}\)

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

\(\color{#0066ff}{输入样例}\)

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

\(\color{#0066ff}{输出样例}\)

1 9   
3 1 4 1 1   
10  
1 1 3 5   
4 1 3 1 1

\(\color{#0066ff}{数据范围与提示}\)

N<=300000, q<=300000,m[1]+m[2]+...+m[q]<=300000

\(\color{#0066ff}{ 题解 }\)

由题可知关键点是比较少的
可以用虚树来做
虚树就是原树中,只保留一些关键点以及互相通达的路径(就是所有LCA也要留下)其余点忽略构成的树
用栈来维护一天深度递增的链,每次来一个点,先找它的LCA
为了防止栈顶与栈顶-1两个点跨过LCA,先把没用的弹掉,特盘一下LCA连边, 然后改变链的方向
构建完虚树后,开始收集ans
dfs1,处理出dep,siz,倍增LCA的一些东西
dfs2,求出虚树上每个点到其子树内的关键的点的最小距离及其编号(用pair),记为d[i]
dfs3,维护当前子树之外的最小距离和编号,使d[i]成为当前点到关键点的最小距离
dfs4,可以发现,对于虚树上的某一点,如果他的孩子的子树中没有关键点,那么这个点就会收集到子树内所有点的贡献(从虚树上的儿子倍增,找到原树上的儿子)
用单步容斥,先加上当前子树的siz,再减去所有有关键点的子树siz
dfs5, 统计子树内有关键点中的贡献,那么两个关键点必为一浅一深,找到中间深度,然后上半部分的所有点(除了当前子树,用siz作差)都是x所属的,剩下的是子树所属的
#include<bits/stdc++.h>
#define LL long long
LL in() {
    char ch; LL x = 0, f = 1;
    while(!isdigit(ch = getchar()))(ch == '-') && (f = -f);
    for(x = ch ^ 48; isdigit(ch = getchar()); x = (x << 1) + (x << 3) + (ch ^ 48));
    return x * f;
}
struct node{
    int to;
    node *nxt;
    node(int to = 0, node *nxt = NULL): to(to), nxt(nxt) {}
    void *operator new (size_t) {
        static node *S = NULL, *T = NULL;
        return (S == T) && (T = (S = new node[1024]) + 1024), S++;
    }
};
const int maxn = 3e5 + 100;
const int inf = 0x7f7f7f7f;
node *h[maxn], *head[maxn];
int dep[maxn], a[maxn], b[maxn], dfn[maxn], p[maxn];
int st[maxn], top;
int n, cnt;
using std::pair;
using std::make_pair;
pair<int, int> d[maxn];
int f[maxn][27], siz[maxn], ans[maxn], up[maxn];
bool vis[maxn];
void add(int from, int to, node **hh) {
    hh[from] = new node(to, hh[from]);
}
void dfs1(int x, int fa) {
    dep[x] = dep[fa] + 1;
    f[x][0] = fa;
    siz[x] = 1;
    dfn[x] = ++cnt;
    for(node *i = h[x]; i; i = i->nxt) 
        if(i->to != fa) dfs1(i->to, x), siz[x] += siz[i->to];
}

void dfs2(int x) {
    if(vis[x]) d[x] = make_pair(0, x);
    else d[x] = make_pair(inf, 0);
    for(node *i = head[x]; i; i = i->nxt)
        dfs2(i->to), d[x] = std::min(d[x], make_pair(d[i->to].first + dep[i->to] - dep[x], d[i->to].second));
}

void dfs3(int x, int dis, int pos) {
    if(d[x] > make_pair(dis, pos)) d[x] = make_pair(dis, pos);
    else dis = d[x].first, pos = d[x].second;
    for(node *i = head[x]; i; i = i->nxt)
        dfs3(i->to, dis + dep[i->to] - dep[x], pos);
}

void dfs4(int x) {
    p[x] = d[x].second;
    ans[p[x]] += siz[x];
    for(node *i = head[x]; i; i = i->nxt) {
        int o = i->to;
        for(int j = 25; j >= 0; j--) if(dep[f[o][j]] > dep[x]) o = f[o][j];
        ans[p[x]] -= siz[up[i->to] = o];
        dfs4(i->to);
    }
}

void dfs5(int x) {
    for(node *i = head[x]; i; i = i->nxt) {
        int u = up[i->to];
        if(p[x] == p[i->to]) ans[p[x]] += siz[u] - siz[i->to];
        else {
            int mid = dep[p[i->to]] + dep[x] - d[x].first;
            //如果是一条链,很容易发现是正确的
            //不是一条直链,因为x最近的不是子树最近的,所以mid一定在x到子树的链上
            mid = mid & 1? (mid + 1) >> 1 : (p[x] < p[i->to]? (mid >> 1) + 1 : (mid >> 1));
            int o = i->to;
            for(int j = 25; j >= 0; j--) if(dep[f[o][j]] >= mid) o = f[o][j];
            ans[p[x]] += siz[u] - siz[o];
            ans[p[i->to]] += siz[o] - siz[i->to];
        }
        dfs5(i->to);
    }
}

void dfs6(int x) {
    up[x] = p[x] = 0;
    for(node *i = head[x]; i; i = i->nxt)
        dfs6(i->to);
    head[x] = NULL;
}

int LCA(int x, int y) {
    if(dep[x] < dep[y]) std::swap(x, y);
    for(int i = 25; i >= 0; i--) if(dep[f[x][i]] >= dep[y]) x = f[x][i];
    if(x == y) return x;
    for(int i = 25; i >= 0; i--) if(f[x][i] != f[y][i]) x = f[x][i], y = f[y][i];
    return f[x][0];
}

int main() {
    n = in();
    int x, y;
    for(int i = 1; i < n; i++) x = in(), y = in(), add(x, y, h), add(y, x, h);
    dfs1(1, 0);
    for(int j = 1; j <= 25; j++)
        for(int i = 1; i <= n; i++)
            f[i][j] = f[f[i][j - 1]][j - 1];
    for(int T = in(); T --> 0;) {
        int num = in();
        for(int i = 1; i <= num; i++) b[i] = a[i] = in(), vis[a[i]] = true;
        std::sort(a + 1, a + num + 1, [](const int &x, const int &y) { return dfn[x] < dfn[y]; });
        st[top = 1] = a[1];
        for(int i = 2; i <= num; i++) {
            int now = a[i], lca = LCA(st[top], now);
            while(top > 1 && dep[st[top - 1]] >= dep[lca]) add(st[top - 1], st[top], head), top--;
            if(st[top] != lca) add(lca, st[top], head), st[top] = lca;
            st[++top] = now;
        }
        while(top > 1) add(st[top - 1], st[top], head), top--;
        int root = st[1];
        dfs2(root);
        dfs3(root, d[root].first, d[root].second);
        dfs4(root), dfs5(root);
        ans[p[root]] += siz[1] - siz[root];
        for(int i = 1; i <= num; i++) printf("%d%c", ans[b[i]], i == num? '\n' : ' ');
        dfs6(root);
        for(int i = 1; i <= num; i++) vis[a[i]] = 0, ans[a[i]] = 0;
    }
    return 0;
}

转载于:https://www.cnblogs.com/olinr/p/10230588.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值