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;
}
posted @ 2019-01-06 21:59  olinr  阅读(224)  评论(0编辑  收藏  举报