树的直径:
树的直径指树上距离最远的两点间的距离,它在树上问题上有许多应用,往往通过树的直径的性质可以将一个高时间复杂度的解法变为线性求解。
树的直径的性质:
(1).对树上的任意一点而言,树上与它距离最远的点一定为树的直径的两个端点的其中之一;
(2).直径两端点一定是两个叶子节点;
(3).对于两棵树,如果第一棵树直径两端点为 (u,v),第二棵树直径两端点为 (x,y),用一条边将两棵树连接,那么新树的直径的两端点一定是 u,v,x,y 中的两个点;
(4).对于一棵树,如果在一个点的上接一个叶子节点,那么最多会改变直径的一个端点;
(5).若一棵树存在多条直径,那么这些直径交于一点且交点是这些直径的中点;
求法:
(1)搜索(dfs/bfs):
步骤:
首先,以任意一点为搜索的起点,进行第一次搜索。此次搜索得到的距起点最远的点即为直径端点之一。然后以该点为起点进行第二次搜索,所得到的距其距离最远的点即为另一个端点。
复杂度: O(n)
方便记录路径
(2)树形dp:
目前还不会,先贴上代码。
代码:
void dp(int st)
{
vis[st] = true; //当前结点已访问
for(int i = head[st]; i != -1; i = edge[i].nxt){
int Eiv = edge[i].v;
if(vis[Eiv]) continue; //不走回头路
dp(Eiv);
ans_max = max(ans_max, d[st] + d[Eiv] + edge[i].w); //更新树的直径(由当前结点两段之和更新)
d[st] = max(d[st], d[Eiv]+edge[i].w); //更新当前结点所能走的最长路径(保留较长的那边)
}
}
题目:
1.Computer HDU - 2196
求距各个点的最远距离,以边权为距离。
bfs+dfs:求出树的直径的端点
#include <bits/stdc++.h>
using namespace std;
const int N=1e4+5;
typedef pair<int,int> P;
vector<P>pic[N];
bool vis[N];
int dis[N],dmax[N];
priority_queue<P,vector<P>,greater<P> >que;
int maxn,ps;
void bfs(int s)
{
maxn=0;
memset(vis,0,sizeof(vis));
memset(dis,0,sizeof(dis));
while(!que.empty())
que.pop();
que.push(make_pair(0,s));
vis[s]=1;
while(!que.empty())
{
P now=que.top();
que.pop();
for(int i=0;i<pic[now.second].size();i++)
{
P tmp=pic[now.second][i];
if(!vis[tmp.second])
{
dis[tmp.second]=dis[now.second]+tmp.first;
if(dis[tmp.second]>maxn)
{
maxn=dis[tmp.second];
ps=tmp.second;
}
que.push(make_pair(dis[tmp.second],tmp.second));
vis[tmp.second]=1;
}
}
}
}
void dfs(int v,int p)
{
for(int i=0;i<pic[v].size();i++)
{
P t=pic[v][i];
if(p!=t.second)
{
dis[t.second]=dis[v]+t.first;
dmax[t.second]=max(dis[t.second],dmax[t.second]);
dfs(t.second,v);
}
}
}
int main()
{
int n;
while(scanf("%d",&n)!=EOF)
{
int v,w,a,b;
memset(dmax,0,sizeof(dmax));
for(int i=1;i<=n;i++)
pic[i].clear();
for(int i=2;i<=n;i++)
{
scanf("%d%d",&v,&w);
pic[i].push_back(make_pair(w,v));
pic[v].push_back(make_pair(w,i));
}
bfs(1);
a=ps;
bfs(a);
b=ps;//直径端点
//cout<<"a="<<a<<" b="<<b<<endl;
memset(dis,0,sizeof(dis));
dfs(a,-1);
memset(dis,0,sizeof(dis));
dfs(b,-1);
for(int i=1;i<=n;i++)
printf("%d\n",dmax[i]);
}
return 0;
}
2.Cow Marathon POJ - 1985
3.Caterpillar POJ - 3310
首先要判断图是否连通,是否有环,这些都可以通过并查集来判断。
然后判断每个点是否要么在直径上,那么直接连在直径上。因为点比较少,所以可以用暴力的方法把所有的直径全部都找出,把直径上的点标记。然后,遍历所有的点,看是否有点连在未标记的点上,即可。
#include <cstdio>//连通图,无环,直径
#include <cstring>
#include <vector>
using namespace std;
const int N=110;
vector<int>pic[N];
int pre[N],ff[N];
bool vis[N];
int n,ps,maxn;
int Find(int x)
{
if(x!=pre[x])
return pre[x]=Find(pre[x]);
else
return x;
}
bool Join(int x,int y)//合并+判环
{
int px=Find(x);
int py=Find(y);
if(px==py)
return false;
pre[px]=py;
return true;
}
bool check()
{
int father=Find(1);
for(int i=1;i<=n;i++)
{
if(father!=Find(i))
return false;
}
return true;
}
void dfs(int v,int p,int d)
{
ff[v]=p;
if(d>maxn)
{
maxn=d;
ps=v;
}
for(int i=0;i<pic[v].size();i++)
{
if(pic[v][i]!=p)
dfs(pic[v][i],v,d+1);
}
}
void solve(int s)
{
while(ff[s]!=-1)
{
vis[s]=1;
s=ff[s];
}
}
int main()
{
int e,cnt=0;
while(scanf("%d",&n),n)
{
scanf("%d",&e);
int u,v;
bool flag=1;
for(int i=1;i<=n;i++)
{
pic[i].clear();
pre[i]=i;
}
memset(vis,0,sizeof(vis));
for(int i=1;i<=e;i++)
{
scanf("%d%d",&u,&v);
pic[u].push_back(v);
pic[v].push_back(u);
if(!Join(u,v))
flag=0;
}
if(flag)
flag=check();
if(flag)
{
for(int i=1;i<=n;i++)//把所有的直径全部找出来;n*n
{
if(!vis[i])
{
maxn=0;
dfs(i,-1,0);
dfs(ps,-1,0);//一个端点找另一个端点
solve(ps);
}
}//标记直径上的所有点
for(int i=1;i<=n;i++)
{
if(vis[i])
continue;
for(int j=0;j<pic[i].size();j++)
{
if(!vis[pic[i][j]])
{
flag=0;
break;
}
}
}
}
if(!flag)
printf("Graph %d is not a caterpillar.\n",++cnt);
else
printf("Graph %d is a caterpillar.\n",++cnt);
}
return 0;
}
4.codeforce 1294F-Three Paths on a Tree
题解