[NOIP2016] 天天爱跑步

前言

看这道题不爽很久了,但一直没有开它,原因是我不会(我太菜了),看了题解还是写不来,因为我不会线段树合并。

然后今天学了dsu on tree这种神奇的科技,成功把它A了,效率吊打线段树合并。

于是写篇题解纪念一下。

题目链接

洛谷P1600 天天爱跑步

算法概述

不带修改的树上路径信息的维护,很容易想到树上差分。

我们考虑一条路径,会对哪些点产生贡献,也就是题目描述中的一个人会被哪些观察员观察到。

假设这条路径为 \((s,t)\),点为 \(x\),我们分两部分考虑:

  • 若点 \(x\)\(s\)\(lca(s,t)\)(含)的路径上,则若满足 \(w[x]=dep[s]-dep[x]\) 则会在 \(x\) 上产生 \(1\) 的贡献。

    将式子变形:\(w[x]+dep[x]=dep[s]\),我们就可以发现一个事情,对于任何一个点 \(p\),这种情况的点数即为所有路径中起点深度为 \(w[p]+dep[p]\) 的数量。

    我们可以形象化地理解为,有 \(m\) 个人,每个人会给 \(s\)\(lca(s,t)\) 路径上的每个点发放一个类型为 \(dep[s]\) 的物品,而我们要求的就是对于每个点 \(x\),其上类型为 \(w[x]+dep[x]\) 的物品有多少个。

    再抽象一点,即一条路径 \(s→lca(s,t)\),会将路径上每个点处权值为 \(dep[s]\) 的位置覆盖一次,最终求每个点 \(x\) 上权值为 \(w[x]+dep[x]\) 的位置被覆盖了几次。

    此时,树上差分已经呼之欲出了。我们在每个点上开一个桶,对于每条路径 \((s,t)\),在点 \(s\) 让下标为 \(dep[s]\) 的值\(+1\),在点 \(fa[lca(s,t)]\) 上让下标为 \(dep[s]\) 的值 \(-1\),其中 \(fa[i]\) 表示 \(i\) 的父亲节点,最后每个点的信息应该是以其为根的子树和,这部分答案即为该点的桶内下标为 \(w[x]+dep[x]\) 的值。

  • 若点 \(x\)\(lca(s,t)\)(不含)的路径上,设路径长度为 \(dis\),则若满足 \(dis-(dep[t]-dep[x])=w[x]\),根据 \(dis=dep[s]+dep[t]-2*dep[lca(s,t)]\),可将式子变形为:\(w[x]-dep[x]=dep[s]-2*dep[lca(s,t)]\)

    那么同样应用树上差分,在点 \(t\) 上使下标为 \(dep[s]-2*dep[lca(s,t)]\) 的值 \(+1\),在 \(lca(s,t)\) 上使下标为 \(dep[s]-2*dep[lca(s,t)]\) 的值 \(-1\),最后每个点的信息统计子树和,答案即为点上下标为 \(w[x]-dep[x]\) 的值。

我们设第一种情况开的桶为 \(up\),第二种情况开的桶为 \(down\),那么对于每个点 \(x\),其最终答案即为 \(up[w[x]+dep[x]]+down[w[x]-dep[x]]\)

但问题是,我们在每个点上开两个桶是显然不可能的,而且就算空间能够支持每个点开两个桶,但我们在统计过程中,合并两个桶的时间复杂度也直接爆炸。

所以我们只能在全局开两个桶。

那么怎么维护子树信息呢?

不带修改的子树信息维护,正是dsu on tree大显身手的领域。

所以在每个点上开个 \(vector\),把每个点要维护的信息放进去,然后dsu on tree,需要统计信息的时候再把信息拿出来统计就好了。

每个点维护信息的时间均摊是 \(\frac{4m}{n}\) 的,每个点最多会被统计 \(O(logn)\) 次,一共有 \(n\) 个点,所以最后的时间复杂度是 \(O(mlogn)\) 的。

参考代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int N=3e5+10;
struct Edge{
	int to,nex;
}edge[N<<1];int idx;
int h[N];

void add_edge(int u,int v){edge[++idx]={v,h[u]};h[u]=idx;}

int fa[N],dep[N],siz[N];
int son[N],top[N],w[N];
vector<int> info[N][4];
int up[N<<1],BUF[N<<2],*down=&BUF[N<<1];
int ans[N];
int n,m;

void dfs1(int p,int f)
{
	fa[p]=f;
	dep[p]=dep[f]+1;
	siz[p]=1;
	int max_son=0;
	for(int i=h[p];~i;i=edge[i].nex)
	{
		int to=edge[i].to;
		if(to==f)continue;
		dfs1(to,p);
		if(siz[to]>max_son)max_son=siz[to],son[p]=to;
		siz[p]+=siz[to]; 
	}
}

void dfs2(int p,int t)
{
	top[p]=t;
	if(!son[p])return;
	dfs2(son[p],t);
	for(int i=h[p];~i;i=edge[i].nex)
	{
		int to=edge[i].to;
		if(to==fa[p]||to==son[p])continue;
		dfs2(to,to);
	}
}

inline int lca(int x,int y)
{
	while(top[x]!=top[y])
	{
		if(dep[top[x]]<dep[top[y]])swap(x,y);
		x=fa[top[x]];
	}
	return dep[x]<dep[y]?x:y; 
}

void calc(int p,int flag,int val)
{
	for(int i=0;i<info[p][0].size();i++)up[info[p][0][i]]+=val;
	for(int i=0;i<info[p][1].size();i++)up[info[p][1][i]]-=val;
	for(int i=0;i<info[p][2].size();i++)down[info[p][2][i]]+=val;
	for(int i=0;i<info[p][3].size();i++)down[info[p][3][i]]-=val;
	for(int i=h[p];~i;i=edge[i].nex)
	{
		int to=edge[i].to;
		if(to==fa[p]||to==flag)continue;
		calc(to,flag,val);
	}
}

void dfs(int p,int keep)
{
	for(int i=h[p];~i;i=edge[i].nex)
	{
		int to=edge[i].to;
		if(to==fa[p]||to==son[p])continue;
		dfs(to,0);
	}
	if(son[p])dfs(son[p],1);
	calc(p,son[p],1);
	ans[p]=up[dep[p]+w[p]]+down[w[p]-dep[p]];
	if(!keep)calc(p,0,-1);
}

int main()
{
	memset(h,-1,sizeof h);
	scanf("%d%d",&n,&m);
	for(int i=1,a,b;i<=n-1;i++)
	{
		scanf("%d%d",&a,&b);
		add_edge(a,b);
		add_edge(b,a);
	}
	for(int i=1;i<=n;i++)scanf("%d",&w[i]);
	
	dfs1(1,0);
	dfs2(1,1);
	
	for(int i=1,s,t;i<=m;i++)
	{
		scanf("%d%d",&s,&t);
		int z=lca(s,t);
		info[s][0].push_back(dep[s]);
		info[fa[z]][1].push_back(dep[s]);
		info[t][2].push_back(dep[s]-2*dep[z]);
		info[z][3].push_back(dep[s]-2*dep[z]);
	}
	
	dfs(1,1);
	
	for(int i=1;i<=n;i++)printf("%d ",ans[i]);
	
	return 0;
}
posted @ 2020-11-03 10:50  魑吻丶殇之玖梦  阅读(170)  评论(0编辑  收藏  举报