题目链接
Codeforces 1294F three paths on a tree
题目大意
给定一棵有 n n n个节点的树,所有边权都为 1 1 1,求三个点 a , b , c a,b,c a,b,c使得出现在 a 到 b , a 到 c , b 到 c a到b,a到c,b到c a到b,a到c,b到c的最短路径中的边数最多。 ( 3 ≤ n ≤ 2 × 1 0 5 ) (3\leq n\leq 2\times10^5) (3≤n≤2×105)。
解题思路
第 1 1 1种方法:
容易证明:三个点a,b,c中一定有树的直径的两个端点a,b,那么对于点c怎么找,我们来看一幅图:
这幅图中的
a
,
b
a,b
a,b两点就是这棵树的直径,那么其实对于这条树的直径的路径中每个点的最长链都包含在了所有红色的边里面。例如我们设根为
d
d
d,点
d
d
d在
p
a
t
h
(
a
,
b
)
path(a,b)
path(a,b)之间,那么点
d
d
d的最长链
p
a
t
h
(
d
,
a
)
path(d,a)
path(d,a)都被红色线段(树的直径)所包含:
既然每个在树的直径
p
a
t
h
(
a
,
b
)
path(a,b)
path(a,b)的点的最长链都包含在其中,所以这些点的次长链都不被包含,那么,我们只要找到所有在
p
a
t
h
(
a
,
b
)
path(a,b)
path(a,b)中的点的次长链中最大的一条,这条次长链的末端就是我们要找的点
c
c
c。
比如对于一个点
e
e
e,它的次长链就是最大的,那么这条链的末端就是点
c
c
c了。
代码有两种写法:
1.树形dp:一次求最长链和次长链。
2.搜索(DFS or BFS):两次求出树的直径两个端点 a , b a,b a,b,标记出 p a t h ( a , b ) path(a,b) path(a,b),然后找出没标记的点中离标记点最远的 1 1 1个就是 c c c。
个人推荐第2种写法,因为第1种写法比较繁琐,容易写错,第2种写法比较简单
第一种写法(树形 d p dp dp):
#include<bits/stdc++.h>
using namespace std;
const int maxl=2e5+10;
int n,cnt,tot,ans,ansnode,ans1,ans2,ans3;
int mx,mxnod;
int ehead[maxl],fir[maxl],sec[maxl],thi[maxl];
struct ed{int to,nxt;}e[maxl<<1];
struct node{int val,v;}a[maxl];
inline void add(int u,int v){
e[++cnt].to=v;e[cnt].nxt=ehead[u];ehead[u]=cnt;
}
inline void prework(){
scanf("%d",&n);
int u,v;
for(int i=1;i<n;i++){
scanf("%d%d",&u,&v);
add(u,v);add(v,u);
}
}
inline void f(int u,int val){
if(val>fir[u])thi[u]=sec[u],sec[u]=fir[u],fir[u]=val;
else if(val>sec[u])thi[u]=sec[u],sec[u]=val;
else if(val>thi[u])thi[u]=val;
}
inline void dfs1(int u,int fa){
int v;
for(int i=ehead[u];i;i=e[i].nxt){
v=e[i].to;
if(v==fa) continue;
dfs1(v,u);
f(u,fir[v]+1);
}
}
inline void dfs2(int u,int fa,int fadis){
int v,val;f(u,fadis);
if(fir[u]+sec[u]+thi[u]>ans){ans=fir[u]+sec[u]+thi[u];ansnode=u;}
for(int i=ehead[u];i;i=e[i].nxt){
v=e[i].to;
if(v==fa) continue;
if(fir[v]+1==fir[u])val=max(fadis+1,sec[u]+1);
else val=max(fadis+1,fir[u]+1);
dfs2(v,u,val);
}
}
inline void dfs(int u,int fa,int dis){
if(dis>mx){mx=dis;mxnod=u;}
int v;
for(int i=ehead[u];i;i=e[i].nxt){
v=e[i].to;
if(v==fa) continue;
dfs(v,u,dis+1);
}
}
inline bool cmp(const node &x,const node &y){
return x.val>y.val;
}
inline void mainwork(){
dfs1(1,0);
dfs2(1,0,0);
int v;
for(int i=ehead[ansnode];i;i=e[i].nxt){
v=e[i].to;mx=0;
dfs(v,ansnode,1);
a[++tot]=node{mx,mxnod};
}
sort(a+1,a+1+tot,cmp);
ans1=a[1].v;ans2=a[2].v;ans3=a[3].v;
if(ans2==0) ans2=ansnode;
if(ans3==0) ans3=2;
}
inline void print(){
printf("%d\n",ans);
printf("%d %d %d",ans1,ans2,ans3);
}
int main(){
prework();
mainwork();
print();
return 0;
}
第二种写法(DFS版本):
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
using namespace std;
const int N = 4e5+10;
int n,m,maxid,maxn;
vector<int> G[N];
int f[N];int d[N];
int vis[N];int path[N];
int book[N];int du[N];
int ans,ct,nid;
void dfs(int x){
for(int i=0;i<G[x].size();i++){
int u=G[x][i];
if(vis[u]==1) continue;
vis[u]=1;
if(d[u]<d[x]+1){
d[u]=d[x]+1;
if(maxn<d[u]){maxid=u;maxn=d[u];}
path[u]=x;
}
dfs(u);
}
return;
}
void dfss(int x,int num){
if(ct<num){ct=num;nid=x;}
for(int i=0;i<G[x].size();i++){
int u=G[x][i];
if(book[u]==1)continue;
book[u]=1;dfss(u,num+1);
}
return;
}
int main(){
int cnt=1,st,ed,idd;
scanf("%d",&n);{
m=n-1;
int flag=0;
for(int i=1;i<=n;i++) G[i].clear();
for(int i=1;i<=n;i++) book[i]=0;
for(int i=1;i<=m;i++){
int ta,tb;
scanf("%d%d",&ta,&tb);
G[ta].push_back(tb);
G[tb].push_back(ta);
}
for(int i=1;i<=n;i++) vis[i]=0;
maxn=0;maxid=0;dfs(1);
int tem=maxid;
for(int i=1;i<=n;i++) vis[i]=0;
for(int i=1;i<=n;i++) path[i]=-1;
for(int i=1;i<=n;i++) d[i]=0;
maxid=0;maxn=0;path[tem]=-1;vis[tem]=1;dfs(tem);st=maxid;
int idd;
for(int i=maxid;i!=-1;i=path[i]){book[i]=1;ed=i;}
for(int i=1;i<=n;i++)
if(book[i]&&i!=st&&i!=ed)idd=i;
ct=0,nid=idd;ans=maxn;
for(int i=1;i<=n;i++)
if(book[i]==1){//注意是从标记的点开始搜未标记的点
ct=0;dfss(i,0);
if(ans<maxn+ct){ans=maxn+ct;idd=nid;}
}
printf("%d\n%d %d %d\n",ans,st,ed,idd);
}
return 0;
}
第 2 2 2种方法:
首先 a , b a,b a,b两个点还是树的直径,点 c c c怎么求?
我们设 d i s ( a , b ) dis(a,b) dis(a,b)是 a a a到 b b b的最短距离,那么答案就是 d i s ( a , b ) + d i s ( a , c ) + d i s ( b , c ) 2 \frac{dis(a,b)+dis(a,c)+dis(b,c)}{2} 2dis(a,b)+dis(a,c)+dis(b,c)。
证明:我们设
1
1
1个点
p
p
p为
l
c
a
(
a
,
b
,
c
)
lca(a,b,c)
lca(a,b,c),那么
d
i
s
(
a
,
b
)
,
d
i
s
(
a
,
c
)
,
d
i
s
(
b
,
c
)
dis(a,b),dis(a,c),dis(b,c)
dis(a,b),dis(a,c),dis(b,c)中其实重复走了
2
2
2遍
p
a
t
h
(
p
,
a
)
,
p
a
t
h
(
p
,
b
)
,
p
a
t
h
(
p
,
c
)
path(p,a),path(p,b),path(p,c)
path(p,a),path(p,b),path(p,c)。
因
为
:
d
i
s
(
a
,
b
)
=
p
a
t
h
(
p
,
a
)
+
p
a
t
h
(
p
,
b
)
d
i
s
(
a
,
c
)
=
p
a
t
h
(
p
,
a
)
+
p
a
t
h
(
p
,
c
)
d
i
s
(
b
,
c
)
=
p
a
t
h
(
p
,
b
)
+
p
a
t
h
(
p
,
c
)
因为:\\dis(a,b)=path(p,a)+path(p,b)\\dis(a,c)=path(p,a)+path(p,c)\\dis(b,c)=path(p,b)+path(p,c)
因为:dis(a,b)=path(p,a)+path(p,b)dis(a,c)=path(p,a)+path(p,c)dis(b,c)=path(p,b)+path(p,c)
BFS版本:
#include<bits/stdc++.h>
using namespace std;
inline int read() {
int x = 0, f = 1; char c = getchar();
while(c < '0' || c > '9') { if(c == '-') f = -1; c = getchar(); }
while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
return x * f;
}
const int maxn = 2e5 + 5;
vector<int> G[maxn];
int vst[maxn], sum[maxn];
int bfs(int u) {
memset(vst, 0, sizeof(vst));
queue<int> que;
que.push(u);
int now;
while(!que.empty()) {
now = que.front();
que.pop();
for(int & x : G[now]) if(!vst[x] && x != u) {
que.push(x);
vst[x] = vst[now] + 1;
}
}
return now;
}
int main() {
int n = read();
for(int i = 1; i < n; i++) {
int a = read(), b = read();
G[a].push_back(b);
G[b].push_back(a);
}
int x = bfs(1), y = bfs(x), ans = 0, best;
for(int i = 1; i <= n; i++) sum[i] += vst[y] + vst[i];
bfs(y);
for(int i = 1; i <= n; i++) {
sum[i] += vst[i];
if(i != x && i != y && sum[i] > ans) ans = sum[i], best = i;
}
printf("%d\n%d %d %d", ans / 2, x, y, best);
return 0;
}
T h a n k s f o r w a t c h i n g ! Thanks\ for\ watching! Thanks for watching!