1. 程式人生 > 實用技巧 >樹上字首和學習應用筆記——樹的直徑

樹上字首和學習應用筆記——樹的直徑

By pjx,拿走請附上連結

樹的直徑

0、前言

這是幾個月前寫了一半的東西好大一口鍋,填個坑

*如果沒有學過書上字首和,建議先閱讀該文獻

一、定義

一些無向邊且權值均為正整數構成的樹上(可以無根),距離最長的2個點的距離為樹的直徑。

二、解法(邊權為例)

1、思路

先任意從一點\(a\) 出發,找到離它最遠的那個點\(b\)
再從\(b\) 出發,找到離\(b\) 最遠的點\(c\)
\(b\)\(c\) 的距離即為樹的直徑。
下面給出證明:

2、證明

不難得證,因為樹上每兩點間只可能有\(1\)條路徑,長度都唯一,
分情況討論:
1、如果\(a\) 點就為樹的直徑的起始點,則找到的\(b\)

點就是終點,再以\(b\) 為起點,又回到\(a\) 點;
2、如果\(a\) 不為樹直徑的兩個端點,則\(b\)\(a\) 最遠。如果\(b\) 不為直徑的端點,那麼一定有一個點比\(b\) 點遠,那個點就是直徑的端點。(因為為正整數權值,\(b\) 連向直徑的端點那一段路也會讓權值變得更大
綜上,\(b\) 便為直徑的一個端點,與\(b\) 點最遠的點就一定是另一個端點了。
得證。

3、樹上字首和 + DFS大法吼啊

如何找出最遠的點呢?字首和是個好東西。先跑一邊從\(a\) 開始的字首和,找到 \(b\),從\(b\) 跑字首和,找到\(c\), 輸出字首和那個值就行了。

三、code:

#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <queue>
using namespace std;
const int N = 10005;
int b[N],cnt,n,m,head[N],total[N],total2[N],b2[N];
struct node{
	int u,v,w,next;
}ed[N];
void add_edge(int u,int v,int w)//鄰接表存圖
{
	cnt++;
	ed[cnt].u=u;
	ed[cnt].v=v;
	ed[cnt].w=w;
	ed[cnt].next=head[u];
	head[u]=cnt;
}
void dfs(int xx)//第一次字首和
{
	//前面沒有賦自己的值,不要弄混兩個字首和 
	for(int i=head[xx];i!=0;i=ed[i].next)
	{
		int temp=ed[i].v;
		if(!b[temp])
		{
			b[temp]=1;
			total[temp]=ed[i].w+total[xx];
			dfs(temp);
		}
	}
}
void dfs2(int xx)//這是第二次字首和
{
	for(int i=head[xx];i!=0;i=ed[i].next)
	{
		int temp=ed[i].v;
		if(!b2[temp])
		{
			b2[temp]=1;
			total2[temp]=ed[i].w+total2[xx];
			dfs2(temp);
		}
	}
}
int main()
{
	int m;
	cin>>n>>m;
	for(int i=1;i<=n-1;i++)
	{
		int x,y,k;
		cin>>x>>y>>k;
		add_edge(x,y,k);
		add_edge(y,x,k);
	}
	b[1]=1;
	dfs(1);//第一遍,從a找到b
	int root,maxn=-1;
	for(int i=1;i<=n;i++)
	{
		if(total[i]>maxn)//找出最大值
		{
			maxn=total[i];
			root=i;
		}
	}
	b2[root]=1;
	dfs2(root);//第二遍,從b再找到c
	int root2,maxn2=-1;
	for(int i=1;i<=n;i++)
	{
		if(total2[i]>maxn2)//找出最大值
		{
			maxn2=total2[i];
			root2=i;
		}
	}
	cout<<maxn2;
	return 0;
}

四、補充練習

poj1985

我的題解