Codeforces 1294F three paths on a tree

2 篇文章 0 订阅
1 篇文章 0 订阅

题目链接

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 ab,ac,bc的最短路径中的边数最多。 ( 3 ≤ n ≤ 2 × 1 0 5 ) (3\leq n\leq 2\times10^5) (3n2×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!

  • 5
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值