【CF860E】Arkady and a Nobody-men 长链剖分

【CF860E】Arkady and a Nobody-men

题意:给你一棵n个点的有根树。如果b是a的祖先,定义$r(a,b)$为b的子树中深度小于等于a的深度的点的个数(包括a)。定义$z(a)=\sum\limits r(a,b)$(b是a的祖先)。要你求出每个点的z值。

$n\le 5\times 10^5$

题解:一开始naive的思路:将所有点按深度排序,将深度相同的点统一处理,统计答案时相当于链加,链求和,用树剖+树状数组搞一搞,时间复杂度$O(n\log^2n)$。

后来看题解发现我这个想法简直菜爆了。我们先从树形DP的角度去想,先给出转移方程:

$ans(x)=ans(fa(x))+dep(x)+ans‘(x)$,ans‘(x)表示与a深度相同的点 对a的贡献。

现在问题变成了求ans‘,我们考虑在每个点对的lca处统计贡献。具体地,我们对于每个点x,维护若干个三元组(d,a,cnt)表示x的子树中有cnt个d级子孙,其中一个子孙为a。DP的过程就相当于在父亲节点处将所有儿子节点的三元组合并,在合并时顺便统计贡献。

具体地,合并方式如下:假如x有两个儿子,它们有三元组$(d,a,cnt_a)$和$(d,b,cnt_b)$,则:

1.ans‘(a)+=dep(x)\times cnt_b
2.ans‘(b)+=dep(x)\times cnt_a
3.得到新三元组(d,a,cnt_a+cnt_b)

但是后面的点 对b的贡献呢?我们发现后面的点 对a和b的贡献就是相同的了,所以我们建一个新图,在新图中从a到b连一条长度为$ans‘(b)-ans‘(a)$的边,最后在新图上DFS一下,最最后统计一下ans数组即可。

以上过程采用长链剖分优化,由于一开始的三元组个数为n,则每次合并都会减少一个三元组,所以时间复杂度O(n)。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <vector>
using namespace std;
const int maxn=500010;
typedef long long ll;
ll ans[maxn];
struct node
{
	int v,x;
	node() {}
	node(int a,int b) {v=a,x=b;}
}mem[maxn<<1],*f[maxn],*now=mem;
int n,cnt,Cnt,rt;
int to[maxn],nxt[maxn],head[maxn],dep[maxn],md[maxn],son[maxn],fa[maxn];
bool vis[maxn];
int To[maxn],Nxt[maxn],Head[maxn];
ll Val[maxn];
inline void add(int a,int b)
{
	to[cnt]=b,nxt[cnt]=head[a],head[a]=cnt++;
}
inline void Add(int a,int b,ll c)
{
	To[Cnt]=b,Val[Cnt]=c,Nxt[Cnt]=Head[a],Head[a]=Cnt++;
}
void dfs1(int x)
{
	md[x]=0;
	for(int i=head[x];i!=-1;i=nxt[i])
	{
		dep[to[i]]=dep[x]+1,dfs1(to[i]);
		if(md[to[i]]+1>md[x])	md[x]=md[to[i]]+1,son[x]=to[i];
	}
}
void dfs2(int x)
{
	if(f[x]==NULL)	f[x]=now,now+=md[x]+2;
	if(son[x])	f[son[x]]=f[x]+1,dfs2(son[x]);
	f[x][0]=node(1,x);
	for(int i=head[x];i!=-1;i=nxt[i])	if(to[i]!=son[x])
	{
		dfs2(to[i]);
		for(int j=0;j<=md[to[i]];j++)
		{
			node a=f[x][j+1],b=f[to[i]][j];
			ans[b.x]+=1ll*dep[x]*a.v;
			ans[a.x]+=1ll*dep[x]*b.v;
			Add(a.x,b.x,ans[b.x]-ans[a.x]);
			f[x][j+1]=node(a.v+b.v,a.x);
		}
	}
}
void dfs3(int x)
{
	for(int i=Head[x];i!=-1;i=Nxt[i])	ans[To[i]]=ans[x]+Val[i],dfs3(To[i]);
}
void dfs4(int x)
{
	for(int i=head[x];i!=-1;i=nxt[i])	ans[to[i]]+=ans[x]+dep[x],dfs4(to[i]);
}
inline int rd()
{
	int ret=0,f=1;	char gc=getchar();
	while(gc<‘0‘||gc>‘9‘)	{if(gc==‘-‘)	f=-f;	gc=getchar();}
	while(gc>=‘0‘&&gc<=‘9‘)	ret=ret*10+(gc^‘0‘),gc=getchar();
	return ret*f;
}
int main()
{
	n=rd();
	int i;
	memset(head,-1,sizeof(head)),memset(Head,-1,sizeof(Head));
	for(i=1;i<=n;i++)
	{
		fa[i]=rd();
		if(!fa[i])	rt=i;
		else	add(fa[i],i);
	}
	dep[rt]=1,dfs1(rt);
	dfs2(rt);
	for(i=0;i<=md[rt];i++)	dfs3(f[rt][i].x);
	dfs4(rt);
	for(i=1;i<=n;i++)	printf("%lld ",ans[i]);
	return 0;
}

原文地址:https://www.cnblogs.com/CQzhangyu/p/8282641.html

时间: 2024-08-04 00:49:33

【CF860E】Arkady and a Nobody-men 长链剖分的相关文章

长链剖分随想

之前写了那么长一篇Blog-现在不如写篇小短文-说一下另一种树链剖分方法--长链剖分的事情.它可以比重链剖分更快地完成一些东西. 树链剖分的原始版本重链剖分非常经典,这里就不从头介绍了. 原本的剖分方法是按照子树大小剖分,与子树点数最多的儿子连成链,所以叫做重链剖分-然后显然就有一个点到根的路径上至多$O(\log n)$条轻边这个性质(因为沿着轻边走,每次子树大小一定小于父亲的一半).有了这个性质就可以做各种路径相关的查询,暴力每次跳到重链开头就好- 而在一些问题里,有这么一种奇妙的剖分方式可

【BZOJ3522】【BZOJ4543】【POI2014】Hotel 树形DP 长链剖分 启发式合并

题目大意 ? 给你一棵树,求有多少个组点满足\(x\neq y,x\neq z,y\neq z,dist_{x,y}=dist_{x,z}=dist_{y,z}\) ? \(1\leq n\leq 100000\) 题解 ? 问题转换为有多少个组点满足\(dist_{i,x}=dist_{i,y}=dist_{i,z}\) ? 我们考虑树形DP ? \(f_{i,j}=\)以\(i\)为根的子树中与\(i\)的距离为\(j\)的节点数 ? \(g_{i,j}=\)以\(i\)为根的子树外选择一个

【CF1009F】 Dominant Indices (长链剖分)

题目链接 \(O(n^2)\)的\(DP\)很容易想,\(f[u][i]\)表示在\(u\)的子树中距离\(u\)为\(i\)的点的个数,则\(f[u][i]=\sum f[v][i-1]\) 长链剖分. \(O(1)\)继承重儿子的信息,再暴力合并其他轻儿子的信息,时间复杂度是线性的. 继承重儿子用指针实现,非常巧妙. #include <cstdio> int xjc; char ch; inline int read(){ xjc = 0; ch = getchar(); while(c

长链剖分总结

长链剖分总结 概念 长链剖分和轻重链剖分十分相似,都是将一棵树节点的信息分成多条链的信息,但是前者是以深度剖分,后者则是以子树大小来剖分. 同时长链剖分还借鉴了$dsu\;on\;tree$的一些$trick$使得它能十分高效地合并子树信息. 性质 破天荒地写了证明 性质一 所有链长度之和为节点数 证明: 每个点在且仅在一条链中 性质二 任意一个点$k$级祖先所在长链的长度一定大于等于$k$ 证明: 假如$y$所在长链的长度小于$k$,那么它所在的链一定不是重链,因为$x-y$这条链显然更优,那

[WC2010]重建计划(长链剖分版)

传送门 Description Solution 时隔多年,补上了这题的长链剖分写法 感觉比点分治要好写的多 我们假设\(pos\)是当前点的\(dfn\),它距离所在链的底端的边的数量是\(len\),距离是\(siz\) 那么我们要求得\(g[pos...pos+len]\) 其中\(g[pos+i]+siz\)表示的是当前点往下长度为\(i\)的最长链的大小 初始情况下,\(g[pos]=-siz[pos]\) 为什么要这么写呢? 因为转移重儿子的时候,我们直接把数组右移了一位,这样子定义

The 2019 ICPC China Nanchang National Invitational and International Silk-Road Programming Contest E. Interesting Trip 长链剖分

题库链接 考虑莫比乌斯, 套上去之后就是变成了统计长度为d的一共有多少路径, 直接长链剖分, 在计蒜客上极度卡常, 卡了一万年才卡过去, 现场好像还有用点分治过去的, 这都能过?? #include<bits/stdc++.h> #define LL long long using namespace std; const int N = (int)5e5 + 7; const int M = 30000; int n, d, a[N], vis[N], miu[M + 1]; int now

【GDOI2020模拟01.16】树上的鼠 (博弈+长链剖分优化dp)

https://gmoj.net/senior/#contest/show/2989/2 思考什么时候先手会赢. 一开始双方都不会希望走到直径的端点上,因为那样对方就可以走直径而使自己输掉. 删掉直径的端点,考虑剩下的树的子问题. 如果又走到端点去了,对面就走到另外一个端点,那我就走到下一层的直径端点去了. 所以大家一直都不想走到直径端点. 一直删,如果最后只剩1一个点,说明先手必败,否则先手必胜. 如果是一条链,就是链的两边的长度不等先手就必胜. 如果是一棵树,考虑随便找一条直径,每次删去它的

长链剖分小结

长链剖分是一种类似\(\rm{dsu\ on\ tree}\)的一种算法,写法类似于普通的树链剖分(重链剖分),只是将\(\rm{siz}\)最大改为了\(\rm{dep}\)最大.可以优化一些与子树深度相关的问题的时间. 性质 1.所有链的长度和为\(O(n)\)级别的 所有的点均只会在一条长链里,所以都只会被计算一次,所以是\(O(n)\)级别的 2.父亲所在的长链长度不会小于其儿子所在的长链 如果上述不成立的话,那么父亲点可以选择该儿子使得长链更长,与原来相矛盾. 这个性质有个推论:对于任

[算法学习] 长链剖分

简介 长链剖分是跟dsu on tree类似的小\(trick\),可以资瓷维护子树中只与深度有关的信息. 并能达到线性的时间复杂度. 算法流程 对于每个点,记录重儿子\(heavy[u]\)表示深度最大的儿子,其余作为轻儿子. 这样我们可以得到若干条互不相交的长链. 在维护信息的过程中,我们先\(O(1)\)继承重儿子的信息,再暴力合并其余轻儿子的信息. 因为每一个点仅属于一条长链,且一条长链只会在链顶位置作为轻儿子暴力合并一次,所以复杂度是线性的. 但是我们发现,这个数组仿佛开不下(大雾),