[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;
}
原文地址:https://www.cnblogs.com/ninedream/p/13918202.html