【洛谷】P5904 [POI2014]HOT-Hotels(长链剖分)

原题链接

题意

给出一棵有 n 个点的树,求有多少组点 (i,j,k) 满足 i,j,k 两两之间的距离都相等。

(i,j,k)(i,k,j) 算作同一组。

1n105

思路

对于每一个节点 u,统计其子树内的答案时,仅 (i,j,k) 满足 LCA(i,j,k)=u 的情况,这样就可以避免重复计算。而对于一棵子树内选择三个节点的情况,又可以分为若干种。

image

第一种情况: LCA(i,j)=LCA(j,k)=LCA(i,k)=u

image

第二种情况: LCA(i,j)=LCA(i,k)=uLCA(j,k)

image

第三种种情况: LCA(j,k)i=u

不难发现,任意一种情况都要求两个点到其 LCA 的距离相等。

于是可以设 fu,k 为以 u 为根的子树中,到 u 距离为 k 的点的数量。

而由于还有第二、三种情况的存在,我们需要维护一个 gu,x,表示在子树 u 中,点对 (x,y)LCA 的距离比 uLCA 的距离大 x 的总数。即 dis(u,LCA)=dis(v,LCA)=dis(u,LCA)+x。其中当 LCA=u 时,x 就表示 xyu 的距离

对于情况三,对答案的贡献为:

ansgu,0

对于情况二,可以枚举一棵子树 gv,j+1,那么也就是 dis(x,LCA)dis(v,LCA) 大了 j+1,也就是比 dis(u,LCA) 大了 j,于是就需要枚举另一个子树中的 fv,j1 来补偿少的距离。于是这部分对答案的贡献就是:

ansx,ysonu,xyfv,j1×gv,j+1

注意到 gu,j 其实也包括了 LCA(x,y)=u,dis(x,u)=j 的情况,所以这个转移也把情况一包含进去了。

对于 f 的转移,比较显然:

fu,jfv,j1

而对于 g 数组的转移,一种是原来就在 v 子树内,即:

gu,jgv,j+1

一种是原来有一个点不在 v 子树内,即:

gu,jx,yinsonu,xyfx,j1×fy,j1

注意到转移都是 j0 开始的,所以可以用前缀和优化。而 j 实际上和树的深度有关系,于是可以考虑长链剖分优化。fu,gufv,gv 的转移有很大一部分只需要更改一下下标,于是可以用到动态分配内存的小技巧。总的时间复杂度就为 O(n)

code:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
const int N=1e5+10;
#define LL long long
LL *f[N],*g[N],tmp[N<<2],*p=tmp,ans;
int n,h[N],son[N],dep[N],idx;
struct edge{int v,nex;}e[N<<1];
void add(int u,int v){e[++idx]=edge{v,h[u]};h[u]=idx;}
void dfs(int u,int fa)
{
	for(int i=h[u];i;i=e[i].nex)
	{
		int v=e[i].v;if(v==fa) continue;dfs(v,u);
		if(!son[u]||dep[v]>dep[son[u]]) son[u]=v;
	}
	dep[u]=dep[son[u]]+1;
}
void solve(int u,int fa)
{
	if(son[u]) f[son[u]]=f[u]+1,g[son[u]]=g[u]-1,solve(son[u],u);
	f[u][0]=1,ans+=g[u][0];
	for(int i=h[u],v;i;i=e[i].nex) if((v=e[i].v)!=fa&&v!=son[u])
	{
		f[v]=p,p+=dep[v]*2;g[v]=p,p+=dep[v]*2;solve(v,u);
		for(int i=0;i<dep[v];i++)
		{
			if(i) ans+=f[u][i-1]*g[v][i];
			ans+=g[u][i+1]*f[v][i];
		}
		for(int i=0;i<dep[v];i++)
		{
			g[u][i+1]+=f[u][i+1]*f[v][i];
			if(i) g[u][i-1]+=g[v][i];
			f[u][i+1]+=f[v][i];
		}
	}
}
int main()
{
	scanf("%d",&n);for(int u,v,i=1;i<n;i++) scanf("%d%d",&u,&v),add(u,v),add(v,u);dfs(1,0);
	f[1]=p;p+=dep[1]*2;g[1]=p;p+=dep[1]*2;solve(1,0);printf("%lld\n",ans);
	return 0;
}
posted @   曙诚  阅读(31)  评论(0编辑  收藏  举报
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
点击右上角即可分享
微信分享提示