目录

P3233-[HNOI2014]世界树

P3233-[HNOI2014]世界树

题目:

题目描述:

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

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

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

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

输入格式:

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

输出格式:

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

样例:

样例输入 1:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
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
2
3
4
5
1 9
3 1 4 1 1
10
1 1 3 5
4 1 3 1 1

思路:

实现:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
#include "ybwhead/ios.h"
const int N = 3e5 + 10, INF = 1e9 + 10;
int n, m, top, cnt;
int dep[N], f[N][20], dfn[N], size[N], lg2[N], sta[N], pnt[N], vis[N], g[N], dp[N], ans[N], tmp[N];
struct graph
{
    int tot;
    int fir[N], to[2 * N], nxt[2 * N];
    graph()
    {
        tot = 0;
        memset(fir, 0, sizeof(fir));
    }
    inline void add(int x, int y)
    {
        to[++tot] = y;
        nxt[tot] = fir[x];
        fir[x] = tot;
        to[++tot] = x;
        nxt[tot] = fir[y];
        fir[y] = tot;
    }
} e1, e2;
inline void dfs(int p) //预处理dfn,dep,size
{
    dfn[p] = ++cnt, size[p] = 1;
    for (register int i = e1.fir[p]; i; i = e1.nxt[i])
    {
        int q = e1.to[i];
        if (q == f[p][0])
            continue;
        dep[q] = dep[p] + 1, f[q][0] = p;
        for (register int j = 1; j <= lg2[dep[q]] + 1; j++)
            f[q][j] = f[f[q][j - 1]][j - 1];
        dfs(q);
        size[p] += size[q];
    }
}
inline int get_lca(int x, int y) //找lca
{
    if (dep[x] < dep[y])
        swap(x, y);
    for (register int i = lg2[dep[x]]; i >= 0; i--)
        if (dep[f[x][i]] >= dep[y])
            x = f[x][i];
    if (x == y)
        return x;
    for (register int i = lg2[dep[x]]; i >= 0; i--)
        if (f[x][i] != f[y][i])
            x = f[x][i], y = f[y][i];
    return f[x][0];
}
inline bool cmp(int a, int b)
{
    return dfn[a] < dfn[b];
}
inline void build(int p) //建立虚树
{
    if (top == 0)
    {
        sta[top = 1] = p;
        return;
    }
    int lca = get_lca(sta[top], p);
    while (top > 1 && dep[lca] < dep[sta[top - 1]])
        e2.add(sta[top - 1], sta[top]), top--;
    if (dep[lca] < dep[sta[top]])
        e2.add(lca, sta[top--]);
    if (top == 0 || sta[top] != lca)
        sta[++top] = lca;
    sta[++top] = p;
}
inline void cal(int x, int y)
{
    int p = y, q = y;
    for (register int i = lg2[dep[p]]; i >= 0; i--)
        if (dep[f[p][i]] > dep[x])
            p = f[p][i];
    ans[g[x]] -= size[p]; //跳到y在原树上对应的x的儿子
    for (register int i = lg2[dep[q]]; i >= 0; i--)
    {
        int llen = dep[y] - dep[f[q][i]] + dp[y], rlen = dep[f[q][i]] - dep[x] + dp[x];
        if (dep[f[q][i]] > dep[x] && (llen < rlen || (llen == rlen && g[y] < g[x])))
            q = f[q][i]; //倍增找到分割点
    }
    ans[g[y]] += size[q] - size[y], ans[g[x]] += size[p] - size[q]; //注意这里要加的是size,因为虚树路径上会有子树
}
inline void dfs1(int p, int fa)
{
    dp[p] = INF;
    for (register int i = e2.fir[p]; i; i = e2.nxt[i])
    {
        int q = e2.to[i];
        if (q == fa)
            continue;
        dfs1(q, p);
        int dis = dep[q] - dep[p]; //注意这里,虚树上的节点并不是连续的
        if (dp[q] + dis < dp[p])
            dp[p] = dp[q] + dis, g[p] = g[q];
        else if (dp[q] + dis == dp[p])
            g[p] = min(g[p], g[q]);
    }
    if (vis[p])
        dp[p] = 0, g[p] = p;
}
inline void dfs2(int p, int fa)
{
    for (register int i = e2.fir[p]; i; i = e2.nxt[i])
    {
        int q = e2.to[i];
        if (q == fa)
            continue;
        int dis = dep[q] - dep[p];
        if (dp[p] + dis < dp[q])
            dp[q] = dp[p] + dis, g[q] = g[p];
        else if (dp[p] + dis == dp[q])
            g[q] = min(g[q], g[p]);
        cal(p, q);
        dfs2(q, p);
    }
    ans[g[p]] += size[p]; //注意这里,还要加上自己
    vis[p] = e2.fir[p] = 0;
}
int main()
{
    lg2[1] = 0;
    for (register int i = 1; i <= 3e5; i++)
        lg2[i] = lg2[i >> 1] + 1;
    int a, b, T;
    yin >> n;
    for (register int i = 1; i < n; i++)
    {
        yin >> a >> b;
        e1.add(a, b);
    }
    dep[1] = 1, dfs(1);
    yin >> T;
    while (T--)
    {
        int flag = 1;
        top = e2.tot = 0;
        yin >> m;
        for (register int i = 1; i <= m; i++)
            yin >> pnt[i], vis[pnt[i]] = 1, ans[pnt[i]] = 0;
        if (!vis[1])
            pnt[++m] = 1, flag = 0;
        for (register int i = 1; i <= m; i++)
            tmp[i] = pnt[i];
        sort(pnt + 1, pnt + m + 1, cmp);
        for (register int i = 1; i <= m; i++)
            build(pnt[i]);
        if (top)
            while (--top)
                e2.add(sta[top], sta[top + 1]);
        dfs1(1, 0), dfs2(1, 0);
        for (register int i = 1; i <= m; i++)
            if (tmp[i] != 1 || flag)
                yout << ans[tmp[i]] << " ";
        yout << endl;
    }
    return 0;
}