赛前训练 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.

浙公网安备 33010602011771号