【单调栈/树链剖分】CF860E Arkady and a Nobody-men

【题目】
CF
给定一棵 n n n个节点的有根树,定义 f ( x , y ) f(x,y) f(x,y)(其中 y y y x x x的祖先)表示 y y y后代中除 x x x以外深度不大于 x x x的节点数。定义 g ( x ) = ∑ f ( x , y ) g(x)=\sum f(x,y) g(x)=f(x,y)。求所有 g ( x ) g(x) g(x) n ≤ 5 × 1 0 5 n\leq 5\times 10^5 n5×105

【解题思路】
一个比较 naive \text{naive} naive的思路就是将节点按深度排序后一层一层做,用树链剖分解决这个东西可以做到 O ( n log ⁡ 2 n ) O(n\log^2 n) O(nlog2n),但是需要一定卡常技巧。我们可以考虑优化这个过程。

我们要求的东西可以表示成这个:
g ( x ) = ∑ d e p ( y ) ≤ d e p ( x ) d e p ( l c a ( x , y ) ) − d e p ( x ) g(x)=\sum_{dep(y)\leq dep(x)} dep(lca(x,y))-dep(x) g(x)=dep(y)dep(x)dep(lca(x,y))dep(x)
等价于:
g ( f a ( x ) ) + ∑ d e p ( y ) = d e p ( x ) d e p ( l c a ( x , y ) ) − d e p ) ( x ) g(fa(x))+\sum_{dep(y)=dep(x)}dep(lca(x,y))-dep)(x) g(fa(x))+dep(y)=dep(x)dep(lca(x,y))dep)(x)
观察到,如果我们先将这些节点按 d f s dfs dfs序排序,对于第 i i i个节点,它前面的节点与它的 l c a lca lca的深度是单调不降的。

那么我们可以维护一个单调栈,若相邻节点与当前节点的 l c a lca lca相同可以将相邻节点合并,因为显然之后它们的 l c a lca lca也是相同的,同样也不存在栈中间会合并的情况。这样每个深度下总的合并次数就是 O ( 点 数 ) O(点数) O()的了。

总复杂度就是 O ( n log ⁡ n ) O(n\log n) O(nlogn) log ⁡ \log log来自于求 l c a lca lca
因为求 l c a lca lca的次数比较多,如果使用 O ( n log ⁡ n ) O(n\log n) O(nlogn)预处理, O ( 1 ) O(1) O(1)询问的欧拉序 l c a lca lca应该会更优秀。不过观察到空间比较紧就没搞了。

【参考代码】

#include<bits/stdc++.h>
#define pb push_back
using namespace std;

typedef long long ll;
const int N=5e5+10;
int n,rt,fc[20],Log[N];
ll ans[N];

namespace IO
{
	int read()
	{
		int ret=0;char c=getchar();
		while(!isdigit(c)) c=getchar();
		while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
		return ret;
	}
	void write(ll x){if(x>9)write(x/10);putchar(x%10^48);}
	void writesp(ll x){write(x);putchar(' ');}
}
using namespace IO;

namespace Tree
{
	int tot,ind;
	int head[N],dep[N],pos[N],fa[20][N];
	vector<int>st[N];
	struct Tway{int v,nex;}e[N<<1];
	void add(int u,int v)
	{
		e[++tot]=(Tway){v,head[u]};head[u]=tot;
		e[++tot]=(Tway){u,head[v]};head[v]=tot;
	}
	void dfs(int x)
	{
		pos[x]=++ind;
		for(int i=1;fc[i]<=dep[x];++i) fa[i][x]=fa[i-1][fa[i-1][x]];
		for(int i=head[x];i;i=e[i].nex)
		{
			int v=e[i].v;
			if(v==fa[0][x]) continue;
			dep[v]=dep[x]+1;fa[0][v]=x;st[dep[v]].pb(v);dfs(v);
		}
	}
	int lca(int x,int y)
	{
		if(dep[x]<dep[y])swap(x,y);
		for(int i=0,t=dep[x]-dep[y];i<20;++i) 
			if(t&fc[i]) x=fa[i][x];
		for(int i=19;~i;--i) if(fa[i][x]^fa[i][y])
			x=fa[i][x],y=fa[i][y];
		return x==y?x:fa[0][x];
	}
}
using namespace Tree;

namespace DreamLolita
{
	struct Stack
	{
		int len,a[N],b[N],c[N];//a=id,b=dep,c=pos
		void clear(){len=0;}
		void push(int x,int y,int z){++len;a[len]=x;b[len]=y;c[len]=z;}
		void pop(){--len;}
		int topa(){return a[len];}
		int topb(){return b[len];}
		ll calctop(){return (ll)b[len]*(c[len]-c[len-1]);}
	}S;
	void initsomething()
	{
		fc[0]=1;for(int i=1;i<20;++i)fc[i]=fc[i-1]<<1;
		for(int i=2;i<N;++i)Log[i]=Log[i>>1]+1;
	}
	bool cmp(int x,int y){return pos[x]<pos[y];}
	void calc(const vector<int> &a)
	{
		ll res=0;S.clear();
		for(int i=0;i<(int)a.size();++i)
		{
			int x=a[i];
			if(!i) S.push(x,0,0);
			else
			{
				for(;;)
				{
					int y=lca(S.topa(),x);
					if(S.topb()<=dep[y]){S.push(x,dep[y]+1,i);break;}
					res-=S.calctop();S.pop();
				}
				res+=S.calctop();
			}
			ans[x]+=res;
		}
	}
	void solve()
	{
		n=read();
		for(int i=1,x;i<=n;++i) 
		{
			x=read();
			if(x) add(i,x); else rt=i;
		}
		initsomething();dfs(rt);
		for(int d=1;d<=n;++d)
		{
			sort(st[d].begin(),st[d].end(),cmp);
			for(int i=0;i<(int)st[d].size();++i) ans[st[d][i]]+=ans[fa[0][st[d][i]]]+d;
			calc(st[d]);reverse(st[d].begin(),st[d].end());calc(st[d]);
		}
		for(int i=1;i<=n;++i) writesp(ans[i]);
	}
}

int main()
{
#ifndef ONLINE_JUDGE
	freopen("CF860E.in","r",stdin);
	freopen("CF860E.out","w",stdout);
#endif
	DreamLolita::solve();
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值