JZOJ5058. 采蘑菇

依然范特西╮ 提交于 2020-08-08 18:48:25

JZOJ5058. 【GDSOI2017模拟4.13】采蘑菇

题目描述:
A君住在魔法森林里,魔法森林可以看做一棵n个结点的树,结点从1~n编号。树中的每个结点上都生长着蘑菇。蘑菇有许多不同的种类,但同一个结点上的蘑菇都是同一种类,更具体地,i号结点上生长着种类为c[i]的蘑菇。
现在A君打算出去采蘑菇,但他并不知道哪里的蘑菇更好,因此他选定起点s后会等概率随机选择树中的某个结点t作为终点,之后从s沿着(s,t)间的最短路径走到t.并且A君会采摘途中所经过的所有结点上的蘑菇。
现在A君想知道,对于每一个结点u,假如他从这个结点出发,他最后能采摘到的蘑菇种类数的期望是多少。为了方便,你告诉A君答案*n的值即可。


数据范围:
30%的数据:n <= 2000
另有20%的数据:给出的第i条边为{i,i+1}
另有20%的数据:蘑菇的种类最多3种
100%的数据:1 <= n <= 3*10^5 , 0 <= c[i] <= n



这道题有虚数做法,有换根线段树做法,也有点分治做法。
它们(除了虚树,因为我并不会)都带有一只log。虽然这道题时限比较宽松,但是有一位dalao想出了O(n)的做法。

我们随便取一个点为根。
定义一下:
s z x sz_{x} szx为以 x x x为根的子树的大小, u p x up_{x} upx为从 x x x到根的路径上,离 x x x最近的且颜色与 x x x相同的祖先的与 x x x在同一条路径上的儿子。
在这里插入图片描述
假设我们做到节点 u u u,颜色为 c c c。考虑经过 x x x的路径的贡献。
黑色的节点表示与u同色的节点。
f f f为离 x x x最近的且颜色与 x x x相同的祖先, x x x u p u up_{u} upu





假设以 x x x为根的子树内的节点为起点,其它节点为终点,那么很显然以 x x x为根的子树内每个节点都能接受到其它节点个数的贡献,也就是 n − s z x n-sz_{x} nszx。同时另 s u m x sum_{x} sumx表示 u p up up值为 x x x的节点,以它们为根的子树大小的总和,贡献加上 s u m x sum_{x} sumx

但是不难发现,这样会算重。
u u u为例,它的子树内,路径上有颜色 c c c的贡献应该至少来自 u u u。同理,它也没有 s u m x sum_{x} sumx的贡献。相当于这一次它们什么贡献都没有获得。

总结的来说, x x x的子树内,我们先全部加上 n − s z x + s u m x n-sz_{x}+sum_{x} nszx+sumx。同时对于 u u u及和它等概念的节点(例如它旁边那个小黑),它们并没有贡献。但是为了方便,不操作等价于再减去 n − s z u p u + s u m u p u n-sz_{up_{u}}+sum_{up_{u}} nszupu+sumupu

那么还有一种情况,节点 u u u没有 u p up up
在这里插入图片描述

我们直接统计这类节点的,以它们为根的子树大小,总和记为 V V V
那么不属于这些节点的点可以获得 V V V的贡献,属于这些节点的点没有获得贡献。

最后,因为没有记以自己为起点的贡献,所以都加上 n n n

对于所有的操作,在 d f s dfs dfs序上,一次操作对应的点都是连续的,所以可以用差分来实现。时间复杂度 O ( N ) O(N) O(N)

最后再次膜拜蔡 B i r d B i r d BirdBird BirdBird,想出这个神仙做法。
并十分感谢Z_Y_S为我这个蒟蒻讲解这个神仙算法。

#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
const int N=3e5+1000;
int n;
int c[N+1];
vector<int> e[N+1],e1[N+1],s[N+1];
int siz[N+1],id[N+1],up[N+1],sum[N+1];
void dfs(int u,int fa)
{
	siz[u]=1,id[u]=++id[0];
	up[u]=s[c[u]][s[c[u]].size()-1];
	if(up[u]==0)
		e1[c[u]].push_back(u);
	for(int i=0;i<e[u].size();i++)
	{
		int v=e[u][i];
		if(v==fa)
			continue;
		s[c[u]].push_back(v);
		dfs(v,u);
		s[c[u]].pop_back();
		siz[u]+=siz[v];
	}
	sum[up[u]]+=siz[u];
}
int tree[N+1];
void update(int a,int b,int c){tree[a]+=c,tree[b+1]-=c;}
void solve(int u,int fa)
{
	update(id[u],id[u]+siz[u]-1,n-siz[u]+sum[u]);
	if(up[u])
		update(id[u],id[u]+siz[u]-1,-n+siz[up[u]]-sum[up[u]]);
	for(int i=0;i<e[u].size();i++)
	{
		int v=e[u][i];
		if(v==fa)
			continue;
		solve(v,u);
	}
}
int num[N+1],ans[N+1];
bool cmp(int x,int y)
{
	return c[x]<c[y];
}
int main()
{
	freopen("mushroom.in","r",stdin);
	freopen("mushroom.out","w",stdout);
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%d",&c[i]),s[i].push_back(0);
	s[0].push_back(0);
	for(int i=1;i<n;i++)
	{
		int a,b;
		scanf("%d %d",&a,&b);
		e[a].push_back(b),e[b].push_back(a);
	}
	dfs(1,0),solve(1,0);
	for(int i=0;i<=n;i++)
	{
		int v=0;
		for(int j=0;j<e1[i].size();j++)
			v+=siz[e1[i][j]];
		update(1,n,v);
		for(int j=0;j<e1[i].size();j++)
			update(id[e1[i][j]],id[e1[i][j]]+siz[e1[i][j]]-1,-v);
	}
	for(int i=1;i<=n;i++)
		ans[i]=ans[i-1]+tree[i];
	for(int i=1;i<=n;i++)
		printf("%d\n",ans[id[i]]+n);
	return 0;
}

Z_Y_Sdalao的代码QWQ。

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!