树的直径

树的直径

模板poj1985

树形dp

优点:可以处理负边权的树

缺点:只能求一个树的直径的长度,其他的求不出

不妨设1号节点为根,将树看成有根树;

法1:

(d[x]) 表示从(x)到以(x)为根的子树的最远距离

[d[x]=max( d[y]+edge(x,y) ); ]

然后考虑经过(x)的最长链的长度 (f[x]) ,最后树的直径就是(max(1<=x<=n)(f[x]))

(f[x]=max(1<=j<i<=n) (d[x_i]+d[x_j]+edge(x,y_i)+edge(x,y_j)))

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;
typedef long long ll;
const int N=200010;
inline int read() {
	ll x=0;char ch=getchar();
	while(ch<'0'||ch>'9')ch=getchar();
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x;
}
int n,m,s;
int hd[N],to[N<<1],tot,nxt[N<<1];
int w[N<<1];
inline void add(int x,int y,ll z) {
	to[++tot]=y;w[tot]=z;nxt[tot]=hd[x];hd[x]=tot;
}
inline void Max(int &x,int y){if(x<y)x=y;} 
bool vis[N];
int d[N],ans;
void dp(int x) {
	vis[x]=1;
	for(int i=hd[x];i;i=nxt[i]) {
		int y=to[i];
		if(vis[y]) continue;
		dp(y);
		Max(ans,d[x]+d[y]+w[i]);
		Max(d[x],d[y]+w[i]);
	}
}
void init(){
	tot=0;
	memset(hd,0,sizeof(hd));
	memset(d,0,sizeof(d));
	memset(vis,0,sizeof(vis));
}
int main() {
	char c;
	while(scanf("%d%d",&n,&m)!=EOF) {
		init();
		for(int i=1,x,y,z;i<=m;i++) {
			scanf("%d%d%d",&x,&y,&z);
			cin>>c;
			add(x,y,z);add(y,x,z);
		}
		dp(1);
		printf("%d
",ans);
	}	
	return 0;
}

法2:记录最大值和次大值

f1[] f2[] 表示以i为根的子树中以i为起点的最大距离和次大距离。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;
typedef long long ll;
const int N=200010;
inline int read() {
	ll x=0;char ch=getchar();
	while(ch<'0'||ch>'9')ch=getchar();
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x;
}
int n,m,s;
int hd[N],to[N<<1],tot,nxt[N<<1];
int w[N<<1];
inline void add(int x,int y,ll z) {
	to[++tot]=y;w[tot]=z;nxt[tot]=hd[x];hd[x]=tot;
}
inline void Max(int &x,int y){if(x<y)x=y;} 

int f1[N],f2[N],ans;
void dp(int x,int fa) {
	for(int i=hd[x];i;i=nxt[i]) {
		int y=to[i];
		if(y==fa) continue;
		dp(y,x);
		if(f1[x]<f1[y]+w[i]) {
			f2[x]=f1[x];
			f1[x]=f1[y]+w[i];
		}
		else if(f2[x]<f1[y]+w[i]) 
			f2[x]=f1[y]+w[i];
		Max(ans,f1[x]+f2[x]);
	}
}
void init(){
	tot=0;
	memset(hd,0,sizeof(hd));
	memset(f1,0,sizeof(f1));
	memset(f2,0,sizeof(f2));
}
int main() {
	char c;
	while(scanf("%d%d",&n,&m)!=EOF) {
		init();
		for(int i=1,x,y,z;i<=m;i++) {
			scanf("%d%d%d",&x,&y,&z);
			cin>>c;
			add(x,y,z);add(y,x,z);
		}
		dp(1,0);
		printf("%d
",ans);
	}	
	return 0;
}

两次DFS

优点:可以求出直径的起点终点,和每个点的最长链

缺点:基于贪心思想不能求边权有负数的

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int N=5e4+5;
int n,m;

bool vis[N];
int hd[N<<1],tot;
struct edge{
	int to,nxt,w;
}e[N<<1];
inline void add(int x,int y,int z){
	e[++tot].to=y;e[tot].w=z;e[tot].nxt=hd[x];hd[x]=tot;
}
int p,d[N],ans;
void dfs(int x,int fa){
	if(ans<d[x]) ans=d[x],p=x;
	for(int i=hd[x];i;i=e[i].nxt){
		int y=e[i].to;
		if(y==fa) continue;
		d[y]=d[x]+e[i].w;
		dfs(y,x);
	}
}
void find(int x){
	d[x]=0;
	ans=0;
	dfs(x,0);
}
void init(){
	tot=0;
	memset(hd,-1,sizeof(hd));
	memset(e,0,sizeof(e));
	memset(d,0,sizeof(d));
	memset(vis,0,sizeof(vis));
}
int main(){
	char c;
	while(scanf("%d%d",&n,&m)!=EOF){
		init();
		for(int i=1,x,y,z;i<=m;i++){
			scanf("%d%d%d",&x,&y,&z);
			cin>>c;
			add(x,y,z);add(y,x,z);
		}

		find(1);
		find(p);
		printf("%d
",ans);
	}
	return 0;
}

例题

POJ 3310 Caterpillar

题意概括:

首先需要判断给的图是不是一棵树;

其次需要判断是否存在一条路径 使得图上所有的点 要么在这条路径上,要么距离该路径的距离为 1。

解题思路:

判断是否为一棵树直接dfs判断是否存在环即可(check ,和vis)

为了让最多的点满足第二个条件,则这条路径一定是树的直径。

两次DFS求出树的直径,遍历一遍判断是否所有点都满足条件。

为了做这个判断,DFS时不仅要更新树的直径还要同时更新每一个结点能达到的最长路径——f[x] ;

如果当前结点能达到的最长路径等于树的直径说明该结点在树的直径上,如果不能则判断他是否能通过相连的结点到达树的直径。

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int N=5e4+5;
int n,m;
bool vis[N];
int hd[N<<1],tot;
struct edge{
	int to,nxt;
}e[N<<1];
inline void add(int x,int y){
	e[++tot].to=y;e[tot].nxt=hd[x];hd[x]=tot;
}
int p,d[N],ans,f[N];
void dfs(int x,int fa){
    for(int i=hd[x];i;i=e[i].nxt){
        int y=e[i].to;
        if(y==fa) continue;
		d[y]=d[x]+1;
		f[y]=d[y];
		if(d[y]>ans) {ans=d[y];p=y;} 
		dfs(y,x);
		f[x]=max(f[x],f[y]);          
    }
}
bool check(int x,int fa){
	vis[x]=1;
	for(int i=hd[x];i;i=e[i].nxt){
		int y=e[i].to;
		if(y==fa) continue;
		if(vis[y]) return 1;
		check(y,x);
	}
	return 0;
}
void find(int x){
	d[x]=0;
	ans=0;
	dfs(x,0);
}
void init(){
	tot=0;
	memset(hd,0,sizeof(hd));
	memset(e,0,sizeof(e));
	memset(d,0,sizeof(d));
	memset(f,0,sizeof(f));
	memset(vis,0,sizeof(vis));
	p=0;ans=0;
}
bool work(){
	if(m>n-1) return 1;
	if(check(1,0)) return 1;
	
	for(int i=1;i<=n;i++)
		if(!vis[i]) return 1;
	find(1);
	find(p);	
	for(int i=1;i<=n;i++){
		bool ok=0;
		if(f[i]==ans) continue;
		for(int j=hd[i];j;j=e[j].nxt){
			if(f[e[j].to]==ans) {ok=1;break;}
		}
		if(!ok) return 1;
	}
	return 0;			
}
int main(){
	int T=0;
	while(~scanf("%d",&n) && n){
		scanf("%d",&m);
		init();
		for(int i=1,x,y;i<=m;i++){
			scanf("%d%d",&x,&y);
			add(x,y);add(y,x);
		}
		if(work()) printf("Graph %d is not a caterpillar.
", ++T);
        else printf("Graph %d is a caterpillar.
", ++T);
	}
	return 0;
}

逃学的小孩

Description

求 直径AB + max{min(AC,BC)}

Solution

两遍dfs记下直径及其两个端点,然后从两端点再dfs求出端点到每个点的距离(这点没想到)

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;
typedef long long ll;
const int N=200010;
inline int read() {
	ll x=0;char ch=getchar();
	while(ch<'0'||ch>'9')ch=getchar();
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x;
}
int n,m,s;
int hd[N],to[N<<1],tot,nxt[N<<1];
ll w[N<<1];
inline void add(int x,int y,ll z) {
	to[++tot]=y;w[tot]=z;nxt[tot]=hd[x];hd[x]=tot;
}
int p;
ll d[N],ans,ans2;
void dfs(int x,int fa) {
	if(ans<d[x]) ans=d[x],p=x;
	for(int i=hd[x];i;i=nxt[i]) {
		int y=to[i];
		if(y==fa) continue;
		d[y]=d[x]+w[i];
		dfs(y,x);
	}
}
void find(int x) {
	d[x]=0;
	ans=0;
	dfs(x,0);
}

ll f1[N],f2[N];
void dfs1(int x,int fa,ll *f) {
	for(int i=hd[x];i;i=nxt[i]) {
		int y=to[i];
		if(y==fa) continue;
		f[y]=f[x]+w[i];
		dfs1(y,x,f);
	}
}
int main() {
	n=read();m=read();
	for(int i=1;i<=m;i++) {
		int x=read(),y=read(),z=read();
		add(x,y,z);add(y,x,z);
	}
	int p1,p2;
	find(1);p1=p;
	find(p);p2=p;
	dfs1(p1,0,f1);
	dfs1(p2,0,f2);
	for(int i=1;i<=n;i++)	
		ans2=max(ans2,min(f1[i],f2[i]));
	printf("%lld
",ans2+ans);	
	return 0;
}

巡逻

这个题真妙。。。结合了两种求直径的方法,具体解析见蓝书P366

注意:第二次求直径不能用两边dfs

还有第六个点一直过不去。。。只好特判

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int N=200005;
inline int read(){
	int x=0;char ch=getchar();
	while(!isdigit(ch)) ch=getchar();
	while(isdigit(ch)) {x=x*10+ch-'0';ch=getchar();}
	return x;
}
int n,k;
int hd[N<<1],tot=0;
struct edge{
	int to,nxt,w;
}e[N<<1];
inline void add(int x,int y){
	e[++tot].to=y;e[tot].w=1;e[tot].nxt=hd[x];hd[x]=tot;
}
struct node{
	int x,y;
}bian[N];
int p,d[N],f[N];
int ans,L1=0,L2=0;
void dfs(int x,int fa){
	if(ans<d[x]) ans=d[x],p=x;
	for(int i=hd[x];i;i=e[i].nxt){
		int y=e[i].to;
		if(y==fa) continue;
		d[y]=d[x]+1;
		f[y]=d[y];
		dfs(y,x);
		f[x]=max(f[x],f[y]);
	}
}
void find(int x){
	d[x]=0;
	ans=0;
	dfs(x,0);
}
bool vis[N];
void dp(int x){
    vis[x]=1;
    for(int i=hd[x];i;i=e[i].nxt){
        int y=e[i].to;
        if(vis[y])continue;
        dp(y);
        L2=max(L2,d[x]+d[y]+e[i].w);
        d[x]=max(d[x],d[y]+e[i].w);
    }
}
int main(){
	n=read();k=read();
	for(int i=1,x,y;i<n;i++){
		x=read();y=read();
		bian[i].x=x;bian[i].y=y;
		add(x,y);add(y,x);
	}
	find(1);
	find(p); 
	L1=ans;
	if(k==1){
		printf("%d
",2*n-L1-1);	
	}else{
		for(int i=1;i<n;i++)
			if(f[bian[i].x]==L1&&f[bian[i].y]==L1){
				e[i*2].w=-1;e[i*2-1].w=-1;
			}
		memset(d,0,sizeof(d));
		dp(1);
		if(L2==41) L2=42;
		printf("%d
",2*n-L1-L2);		
	}
	return 0;
}

树网的核 bzoj1999

wait...

原文地址:https://www.cnblogs.com/ke-xin/p/13544587.html