洛谷传送门
BZOJ传送门
题目描述
世界树是一棵无比巨大的树,它伸出的枝干构成了整个世界。在这里,生存着各种各样的种族和生灵,他们共同信奉着绝对公正公平的女神艾莉森,在他们的信条里,公平是使世界树能够生生不息、持续运转的根本基石。
世界树的形态可以用一个数学模型来描述:世界树中有 n n 个种族,种族的编号分别从 到 n n ,分别生活在编号为 到 n n 的聚居地上,种族的编号与其聚居地的编号相同。有的聚居地之间有双向的道路相连,道路的长度为 。保证连接的方式会形成一棵树结构,即所有的聚居地之间可以互相到达,并且不会出现环。定义两个聚居地之间的距离为连接他们的道路的长度;例如,若聚居地 a a 和 之间有道路, b b 和 之间有道路,因为每条道路长度为 1 1 而且又不可能出现环,所以 与 c c 之间的距离为 。
出于对公平的考虑,第 i i 年,世界树的国王需要授权 个种族的聚居地为临时议事处。对于某个种族 x x ( 为种族的编号),如果距离该种族最近的临时议事处为 y y ( 为议事处所在聚居地的编号),则种族 x x 将接受 议事处的管辖(如果有多个临时议事处到该聚居地的距离一样,则 y y 为其中编号最小的临时议事处)。
现在国王想知道,在 年的时间里,每一年完成授权后,当年每个临时议事处将会管理多少个种族(议事处所在的聚居地也将接受该议事处管理)。 现在这个任务交给了以智慧著称的灵长类的你:程序猿。请帮国王完成这个任务吧。
输入输出格式
输入格式:
第一行为一个正整数 n n ,表示世界树中种族的个数。接下来行,每行两个正整数 x,y x , y ,表示 x x 聚居地与聚居地之间有一条长度为 1 1 的双向道路。接下来一行为一个正整数,表示国王询问的年数。接下来 q q 块,每块两行:第块的第一行为 1 1 个正整数,表示第 i i 年授权的临时议事处的个数。第块的第二行为 m[i] m [ i ] 个正整数 h[l] h [ l ] 、 h[2] h [ 2 ] 、…、 h[m[i]] h [ m [ i ] ] ,表示被授权为临时议事处的聚居地编号(保证互不相同)。
输出格式:
输出包含 q q 行,第行为 m[i] m [ i ] 个整数,该行的第 j(j=1,2...,,m[i]) j ( j = 1 , 2... , , m [ i ] ) 个数表示第 i i 年被授权的聚居地的临时议事处管理的种族个数。
输入输出样例
输入样例#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
说明
N≤300000,q≤300000,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("");
}
}