赛前训练 12 树的直径、中心和重心

A

树的直径板子.

B

注意到树的直径有个性质:所有节点到其他点的最远距离一定在直径的端点处取到.怎么证明请查阅往期笔记.

这样,我们把直径留着,将其他点依次和端点匹配,最后加上直径的贡献就得到了第一问的答案.

那么如何输出方案?画个图可以发现取出其他点的过程类似于拓扑排序,于是我们模拟一遍拓扑排序即可.最后直径跳 fa 输出就好啦.

实现
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <stdlib.h>
#include <vector>
#include <queue>
#include <cmath>
#include <stack>
#include <map>
#include <set>
#define int long long
using namespace std;

const int N=2e5+5;
int n,x,y;
int dis[N],dis2[N],dg[N],f[N];
bool vis[N];
struct NODE{
	int v,w;
};
vector<NODE> G[N];

void dfs(int cur,int fa,int *d){
	f[cur]=fa;
	for(auto [i,w]:G[cur]){
		if(i==fa)
			continue;
		d[i]=d[cur]+1;
		dfs(i,cur,d);
	}
}
void mark(){
	int cur=x;
	while(cur!=y){
		vis[cur]=1;
		cur=f[cur];
	}
	vis[y]=1;
}

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin>>n;
	for(int i=1,u,v;i<n;i++){
		cin>>u>>v;
		G[u].push_back({v,0});
		G[v].push_back({u,0});
		dg[u]++,dg[v]++;
	}
	dfs(1,0,dis);
	for(int i=1;i<=n;i++)
		if(dis[x]<dis[i])
			x=i;
	memset(dis,0,sizeof dis);
	dfs(x,0,dis);
	for(int i=1;i<=n;i++)
		if(dis[y]<dis[i])
			y=i;
	dfs(y,0,dis2);
	int ans=dis[y]*(dis[y]+1)/2;
	mark();
	for(int i=1;i<=n;i++)
		if(!vis[i])
			ans+=max(dis[i],dis2[i]);
	cout<<ans<<'\n';
	queue<int> q;
	for(int i=1;i<=n;i++)
		if(dg[i]==1&&!vis[i])
			q.push(i);
	while(!q.empty()){
		int cur=q.front(),cur2=y;
		if(dis[cur]>dis2[cur])
			cur2=x;
		cout<<cur<<' '<<cur2<<' '<<cur<<'\n';
		q.pop();
		for(auto [i,w]:G[cur]){
			if(vis[i])
				continue;
			dg[i]--;
			if(dg[i]==1)
				q.push(i);
		}
	}
	int cur=x;
	while(cur!=y){
		cout<<cur<<' '<<y<<' '<<cur<<'\n';
		cur=f[cur];
	}
	return 0;
}

C

一个强力性质:两棵树连在一起后要求直径最大必须连中心,因为最长链都经过中心.于是这个题枚举断开的边然后暴力求中心连边最后求直径取 \(\max\) 即可.怎么都是性质题呜呜呜被薄纱了

实现
#include<bits/stdc++.h>
#define int long long
using namespace std;

const int N=5e3+5;
int n,ans=1e18,f[N];
int dis,dis1,dis2;
struct E{ int v,w; };
vector<E> T[N<<1];
int len1[N],len2[N],up[N];
bool vis[N];
int u[N],v[N],w[N];

inline int read(){
    int x=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-') f=-1;
        ch=getchar();
    }
    while(ch>='0' && ch<='9')
        x=x*10+ch-'0',ch=getchar();
    return x*f;
}
void dfsd(int cur,int fa){
    vis[cur]=1;
	for(auto i:T[cur]){
		if(i.v==fa) continue;
		dfsd(i.v,cur);
		if(len1[i.v]+i.w>len1[cur])
			len2[cur]=len1[cur],
			len1[cur]=len1[i.v]+i.w;
		else if(len1[i.v]+i.w>len2[cur])
			len2[cur]=len1[i.v]+i.w;
	}
    dis=max(dis,len1[cur]+len2[cur]);
}
void dfsu(int cur,int fa){
	for(auto i:T[cur]){
		if(i.v==fa) continue;
		up[i.v]=up[cur]+i.w;
		if(len1[i.v]+i.w!=len1[cur])
			up[i.v]=max(up[i.v],len1[cur]+i.w);
		else
			up[i.v]=max(up[i.v],len2[cur]+i.w);
		dfsu(i.v,cur);
	}
}
int fnd(int x){
	return (f[x]==x?x:f[x]=fnd(f[x]));
}
void uni(int x,int y){
	x=fnd(x),y=fnd(y);
	if(x!=y) f[x]=y;
}


signed main(){
	//ios::sync_with_stdio(0);
	n=read();
	for(int i=1;i<n;i++)
		u[i]=read(),v[i]=read(),w[i]=read();
	for(int i=1;i<n;i++){
		for(int j=1;j<=n;j++) f[j]=j;
        for(int j=1;j<=n;j++) T[j].clear();
        for(int j=1;j<=n;j++) len1[j]=len2[j]=up[j]=0; 
        dis1=dis2=0;
        for(int j=1;j<n;j++)
			if(j!=i) 
				uni(u[j],v[j]),
				T[u[j]].push_back({v[j],w[j]}),
				T[v[j]].push_back({u[j],w[j]});

		dis=0,dfsd(u[i],0);
        dis1=dis;
        dis=0,dfsd(v[i],0);
        dis2=dis;
        dfsu(u[i],0),dfsu(v[i],0);

		int minx=1e18,miny=1e18;
		for(int j=1;j<=n;j++){
			if(fnd(j)==fnd(u[i])&&minx>max(len1[j],up[j]))
				minx=max(len1[j],up[j]);
			if(fnd(j)==fnd(v[i])&&miny>max(len1[j],up[j]))
				miny=max(len1[j],up[j]);
		}
		
		ans=min(ans,max({dis1,dis2,w[i]+minx+miny}));
	}
	cout<<ans;
	return 0;
}

D

因为 \(F\) 位于直径上且具有长度限制,所以很容易想到双指针维护 \(F\).

求出 \(F\) 之后,设其上端点为 \(i\),下端点为 \(j\),那么它的 \(\text{ECC}\) 存在三种情况:

  • 直径的下端点到 \(j\) 的长度

  • 直径上端点到 \(i\) 的长度

  • 直径外部的点连到 \(F\) 的长度.

前两个维护 \(F\) 的时候可以顺便求出,而第三个则可以对直径上每个点跑一遍 DFS 求出最长距离即可.三者取 \(\max\) 即为答案.时间复杂度 \(\mathcal{O}(n)\).

实现
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <stdlib.h>
#include <vector>
#include <queue>
#include <cmath>
#include <stack>
#include <map>
#include <set>
#define int long long
using namespace std;

const int N=1e3+5;
int n,s;
int dis[N],f[N];
bool vis[N];
struct NODE{
	int v,w;
};
vector<NODE> G[N];

void dfs(int cur,int fa){
	f[cur]=fa;
	for(auto [i,w]:G[cur]){
		if(i==fa||vis[i])
			continue;
		dis[i]=dis[cur]+w;
		dfs(i,cur);
	}
}

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin>>n>>s;
	for(int i=1,u,v,w;i<n;i++){
		cin>>u>>v>>w;
		G[u].push_back({v,w});
		G[v].push_back({u,w});
	}
	dfs(1,0);
	int p=0,ans=1e18;
	for(int i=1;i<=n;i++)
		if(dis[p]<dis[i])
			p=i;
	memset(dis,0,sizeof dis);
	dfs(p,0);
	for(int i=1;i<=n;i++)
		if(dis[p]<dis[i])
			p=i;
	for(int i=p,j=p;i;i=f[i]){
		vis[i]=1;
		for(;dis[j]-dis[i]>s;j=f[j]);
		ans=min(ans,max(dis[i],dis[p]-dis[j]));
	}
	for(int i=p;i;i=f[i])
		dis[i]=0,dfs(i,f[i]);
	for(int i=1;i<=n;i++)
		ans=max(ans,dis[i]);
	cout<<ans;
	return 0;
}

总结

这三个知识点对应的性质记得考前记背.

树上路径转序列先以一个端点进行 dfs.

posted @ 2025-10-19 21:44  _KidA  阅读(1)  评论(0)    收藏  举报