JZOJ5058. 【GDSOI2017模拟】采蘑菇震惊!O(n)时间复杂度

JZOJ5058. 【GDSOI2017模拟】采蘑菇 震惊!O(n)
转载
题目描述:

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)的做法。

我们随便取一个点为根。定义一下:

size[x]为以x为根的子树的大小,up[x]​为从x到根的路径上,离x最近的且颜色与x相同的祖先的与x在同一条路径上的儿子。

在这里插入图片描述

假设我们做到节点u,颜色为c。考虑经过x的路径的贡献。
黑色的节点表示与u同色的节点。f为离x最近的且颜色与x相同的祖先,x为up[u]。
假设以x为根的子树内的节点为起点,其它节点为终点,那么很显然以x为根的子树内每个节点都能接受到其它节点个数的贡献,也就是n−size[x]。同时另sum[x]表示up值为x的节点,以它们为根的子树大小的总和,贡献加上sum[x]。

但是不难发现,这样会算重。
以u为例,它的子树内,路径上有颜色c的贡献应该至少来自u。同理,它也没有sum[x]的贡献。相当于这一次它们什么贡献都没有获得。总结的来说,x的子树内,我们先全部加上n−size[x]+sum[x]。同时对于u及和它等概念的节点(例如它旁边那个小黑),它们并没有贡献。但是为了方便,不操作等价于再减去n−size[up[u]]+sum[up[u]]。那么还有一种情况,节点u没有up。

在这里插入图片描述
我们直接统计这类节点的,以它们为根的子树大小,总和记为V。
那么不属于这些节点的点可以获得V的贡献,属于这些节点的点没有获得贡献。
最后,因为没有记以自己为起点的贡献,所以都加上n。对于所有的操作,在dfs序上,一次操作对应的点都是连续的,所以可以用差分来实现。时间复杂度O(N)。
最后再次膜拜蔡BirdBird,想出这个神仙做法。

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;

const int N = 3e5 + 5;
int n, c[N], u, v, la[N], cnt, up[N], cnt_q, laq[N], sz[N], son[N], id[N], tot, f[N], las[N], cnt_s, sum[N], Q[N];
struct edge {int en, ne;}e[N * 2]; struct cadena{int en, ne;}q[N], s[N]; ll t[N * 4], lazy[N * 4];

void down(int x, int l, int r) {if(!lazy[x]) return; t[x] += lazy[x] * (r - l + 1); if(l != r) lazy[x * 2] += lazy[x], lazy[x * 2 + 1] += lazy[x]; lazy[x] = 0;}
void add(int u, int v) {e[++cnt].en = v, e[cnt].ne = la[u], la[u] = cnt, e[++cnt].en = u, e[cnt].ne = la[v], la[v] = cnt;}
bool cmp(int x, int y) {return c[x] < c[y];}

void modify(int x,int l,int r,int le,int ri,ll v)
{
	if(le > ri || l > ri || r<le) return;
	if(le <= l && r <= ri) {lazy[x] += v, down(x, l, r); return;}
	down(x, l, r); int mid = l + r >> 1;
	modify(x * 2, l, mid, le, ri, v);
	modify(x * 2 + 1,mid + 1, r, le, ri, v);
	t[x] = t[x * 2 + 1] + t[x * 2];
}

ll query(int x, int l, int r, int p)
{
	down(x, l, r);
	if(l == r) return t[x]; int mid = l + r >> 1;
	if(p <= mid) return query(x * 2, l, mid, p);
	else return query(x * 2 + 1, mid + 1, r, p);
}

void dfs1(int x, int fa)
{
	sz[x] = 1, f[x] = fa, up[x] = q[laq[c[x]]].en, s[++cnt_s] = (cadena){x, las[up[x]]}, las[up[x]] = cnt_s;
	for(int i = la[x], v = e[i].en; i; i = e[i].ne, v = e[i].en)
		if(v != fa)
		{
			q[++cnt_q] = (cadena){v, laq[c[x]]}, laq[c[x]] = cnt_q;
			dfs1(v, x), laq[c[x]] = q[laq[c[x]]].ne, sz[x] += sz[v];
			if(sz[v] > sz[son[x]]) son[x] = v;
		}
	sum[up[x]] += sz[x];
}

void dfs2(int x, int top)
{
	id[x] = ++tot;
	if(son[x]) dfs2(son[x], top);
	for(int i = la[x], v = e[i].en; i; i = e[i].ne, v = e[i].en)
		if(v != f[x] && v != son[x]) dfs2(v, x);
}

void dfs3(int x)
{
	modify(1, 1, tot, id[x], id[x] + sz[x] - 1, n - sz[x] + sum[x]);
	if(up[x]) modify(1, 1, tot, id[x], id[x] + sz[x] - 1, -n + sz[up[x]] - sum[up[x]]);
	for(int i = la[x], v = e[i].en; i; i = e[i].ne, v = e[i].en) if(v != f[x]) dfs3(v);
}

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]);
	for(int u, v, i = 1; i < n; i++) scanf("%d%d", &u, &v), add(u, v);
	dfs1(1, 0), dfs2(1, 0), dfs3(1);
	for(int i = las[0]; i; i = s[i].ne) Q[++Q[0]] = s[i].en;
	sort(Q + 1, Q + 1 + Q[0], cmp);
	for(int l = 1, r; l <= Q[0]; l = r)
	{
		if(Q[l] == 1) {r = l + 1; continue;}
		ll ret = 0; r = l;
		while(c[Q[r]] == c[Q[l]]) ret += sz[Q[r]], r++; modify(1, 1, tot, 1, tot, ret);
		for(int i = l; i < r; i++) modify(1, 1, tot, id[Q[i]], id[Q[i]] + sz[Q[i]] - 1, -ret);
	}
	for(int i = 1; i <= n; i++) printf("%lld\n", query(1, 1, tot, id[i]) + n);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值