虚树(bzoj 3572: [Hnoi2014]世界树)

例题:

一棵n个节点的树,m次查询,每次查询给你一个点集U,对于树上的所有节点x(x∉U),你要找到一个点y(y∈U)满足y点离x点最近且标号最小,表示x点受y点管辖,而你的任务就是对于每次查询输出U集合中的每个点各管辖多少点

n<=300000,m<=300000,∑xi<=300000


m次查询,每次遍历整棵树显然不可能,但是从上面蓝色的条件可以得知平均每次询问的点是非常少的

这样的话考虑每次只遍历询问的点复杂度就会从O(nm)到O(∑xi)大幅降低

问题就是如何将询问的点拉出来,看下图


其中红色点为当前查询的点,下面有红线的点是其中某一对红点的LCA

把下面有红线的点和红点拉出来可以得出一棵树,就是虚树

所以我们只要每次查询把虚树拉出来然后求就可以降低一维的复杂度

虽然虚树中多了很多LCA,但是可以证明这些“额外”点的数量平均不会超过一倍,所以对复杂度无影响


主要是如何求出虚树

步骤:

①求出所有点的dfs序和深度d[],并对整棵树进行倍增LCA的预处理

②对于每次查询,用一个栈维护,按照dfs序遍历当次查询的所有点

③对于当前点x,如果栈顶节点y不是x的祖先,弹出栈顶y,father[y] = lca(y, x)(前提y点的father[]深度比lca小)

直到栈顶节点是x祖先结束

④这个时候y一定是x祖先,如果y!=lca(y, x),那么lca(y, x)一定也是虚树中的节点(对应上图中底下画红线的节点),将其加入虚树中,并加入栈中,它的父亲就是y

⑤将当前点x加入栈中,它的父亲为lca(y, x),继续执行步骤③直到所有节点遍历结束


到这里虚树就求出来了,就变成一道普通的题了,再想怎么求出答案

t[]:里面存的全部都是虚树中的点

g[p] = (x, y):x是权值,y是离p最近的管辖点,x是距离,其中p一定是虚树中的点,和t[]对应

fa[]:虚树中每个点的父亲

w[p]:虚树中p点和它父亲之间的距离


考虑树形DP:

①对于虚树中的所有点,先求出离它最近的管辖点,当然很多情况都是它本身

(别忘了有LCA,所以虚树中并不是所有点都是管辖点,具体求法见代码)

然后DFS整棵虚树,因为已经是按dfs序排好了,所以直接遍历就相当于DFS了

求出虚树中每条边对答案的贡献就好了,例如上面的图,求出边E(14, 18)对答案的贡献就相当于求原树中点17对答案的贡献,当然求完之后会漏掉一些点(例如点3, 6, 8, 9, 15全部都会被漏掉)但这些点非常好办不是么,它们一定属于离它最近的管辖点


3572: [Hnoi2014]世界树

Time Limit: 20 Sec   Memory Limit: 512 MB
Submit: 1852   Solved: 981
[ Submit][ Status][ Discuss]

Description

世界树是一棵无比巨大的树,它伸出的枝干构成了整个世界。在这里,生存着各种各样的种族和生灵,他们共同信奉着绝对公正公平的女神艾莉森,在他们的信条里,公平是使世界树能够生生不息、持续运转的根本基石。
世界树的形态可以用一个数学模型来描述:世界树中有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年的时间里,每一年完成授权后,当年每个临时议事处将会管理多少个种族(议事处所在的聚居地也将接受该议事处管理)。 现在这个任务交给了以智慧著称的灵长类的你:程序猿。请帮国王完成这个任务吧。

Input

第一行为一个正整数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]],表示被授权为临时议事处的聚居地编号(保证互不相同)。

Output

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

Sample Input

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

Sample Output

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


就是上面的例题

#include<stdio.h>
#include<vector>
#include<algorithm>
#include<map>
using namespace std;
vector<int> G[300005];
pair<int, int> g[300005];
int Time, n, m, ec, f[300005][20], dfn[300005], size[300005], d[300005];
int h[300005], id[300005], st[300005], father[300005], t[300005], val[300005], ans[300005], w[300005];
bool comp(int x, int y)
{
	if(dfn[x]<dfn[y])
		return 1;
	return 0;
}
void Sech(int u)
{
	int i, v, j;
	dfn[u] = ++Time;
	size[u] = 1;
	for(i=0;i<G[u].size();i++)
	{
		v = G[u][i];
		if(v==f[u][0])
			continue;
		f[v][0] = u;
		for(j=0;f[f[v][j]][j]!=0;j++)
			f[v][j+1] = f[f[v][j]][j];
		d[v] = d[u]+1;
		Sech(v);
		size[u] += size[v];
	}
}
int Find(int u, int dep)
{
	int i;
	for(i=19;i>=0;i--)
	{
		if(d[f[u][i]]>=dep)
			u = f[u][i];
	}
	return u;
}
int LCA(int u, int v)
{
	int i;
	if(d[u]<d[v])
		swap(u, v);
	for(i=19;i>=0;i--)
	{
		if(d[f[u][i]]>=d[v])
			u = f[u][i];
	}
	if(u==v)
		return u;
	for(i=19;i>=0;i--)
	{
		if(f[u][i]!=f[v][i])
			u = f[u][i], v = f[v][i];
	}
	return f[u][0];
}
int main(void)
{
	int x, y, i, q, m, cnt, top, p, lca, fa, sum, mid;
	scanf("%d", &n);
	for(i=1;i<=n-1;i++)
	{
		scanf("%d%d", &x, &y);
		G[x].push_back(y);
		G[y].push_back(x);
	}
	scanf("%d", &q);
	d[1] = 1;
	Sech(1);
	while(q--)
	{
		cnt = 0;
		scanf("%d" ,&m);
		for(i=1;i<=m;i++)
		{
			scanf("%d", &h[i]);
			t[++cnt] = id[i] = h[i];
			g[h[i]] = make_pair(0, h[i]);
			ans[h[i]] = 0;
		}
		top = 0;
		sort(h+1, h+m+1, comp);
		for(i=1;i<=m;i++)
		{
			if(top==0)
			{
				st[++top] = h[i];
				father[st[top]] = 0;
			}
			else
			{
				lca = LCA(h[i], st[top]);
				for(;d[st[top]]>d[lca];top--)
				{
					if(d[st[top-1]]<=d[lca])
						father[st[top]] = lca;
				}
				if(st[top]!=lca)
				{
					t[++cnt] = lca;
					g[lca] = make_pair(1044266558, 0);
					father[lca] = st[top];
					st[++top] = lca;
				}
				father[h[i]] = lca;
				st[++top] = h[i];
			}
		}
		sort(t+1, t+cnt+1, comp);
		for(i=1;i<=cnt;i++)
		{
			p = t[i];
			val[p] = size[p];
			if(i>1)
				w[p] = d[p]-d[father[p]];
		}
		for(i=cnt;i>=2;i--)
		{
			p = t[i];
			fa = father[p];
			g[fa] = min(make_pair(g[p].first+w[p], g[p].second), g[fa]);
		}
		for(i=2;i<=cnt;i++)
		{
			p = t[i];
			fa = father[p];
			g[p] = min(make_pair(g[fa].first+w[p], g[fa].second), g[p]);
		}
		for(i=1;i<=cnt;i++)
		{
			p = t[i];
			fa = father[p];
			if(i==1)
				ans[g[p].second] += n-size[p];
			else
			{
				x = Find(p, d[fa]+1);
				sum = size[x]-size[p];
				val[fa] -= size[x];
				if(g[fa].second==g[p].second)
					ans[g[p].second] += sum;
				else
				{
					mid = d[p]+g[p].first-(g[fa].first+g[p].first+w[p])/2;
					if((g[fa].first+g[p].first+w[p])%2==0 && g[p].second>g[fa].second)
						++mid;
					y = size[Find(p, mid)]-size[p];
					ans[g[p].second] += y;
					ans[g[fa].second] += sum-y;
				}
			}
		}
		for(i=1;i<=cnt;i++)
			ans[g[t[i]].second] += val[t[i]];
		for(i=1;i<=m;i++)
			printf("%d ", ans[id[i]]);
		puts("");
	}
	return 0;
}


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值