樹的直徑
樹的直徑,是指樹上最長的一條鏈。
求樹的直徑有兩種方法
\(1.DP\):\(d1[u]\)表示\(u\)到達子樹中葉子節點的最長鏈,\(d2[u]\)表示\(u\)到達子樹中葉子節點的次長鏈,兩條鏈不能有交集,只需要對每個節點做以下更新同時維護最大值最小值即可
void dfs1(int u,int fa)
{
for(int i=head[u];i;i=e[i].nxt)
{
int ev=e[i].v;
if(ev==fa) continue;
dfs1(ev,u);
if(d1[ev]+e[i].w>d1[u])
d2[u]=d1[u],d1[u]=d1[ev]+e[i].w;
else if(d1[ev]+e[i].w>d2[u])
d2[u]=d1[ev]+e[i].w,c2[u]=ev;
}
}
這樣維護保證了不會出現交集,不用\(d2[ev]\)更新是因為我們找的是最大值,如果\(d1[ev]\)不能跟新\(d2[ev]\)更不能更新,如果前者可以更新再用\(d2[ev]\)更新也沒必要了
最后如此統計答案即可
ans=max(d1[i]+d2[i],ans);(1<=i<=n)
\(2.\)兩遍\(BFS/DFS\)
具體流程:先隨意指定一個節點(記為\(z\))作為起點搜索到距離起點最遠的點,記這個點\(x\),從\(x\)開始搜索到距離\(x\)最遠的點\(y\),因為樹上路徑唯一,\(x->y\)就是直徑
證明:
假設\(x\)在直徑上,因為第一次搜索是找的最遠節點,它一定是葉子節點,所以它一定是直徑的一端,從\(x\)找最遠距離一定是直徑。
所以我們只需要證明\(x\)一定在直徑上即可,考慮反證法,假設\(x\)不在直徑上,而直徑真實的端點是\(q\)
分兩種情況考慮:
\((1)k\)在直徑上,對於這種情況,因為第一次找的是最遠距離,也就是說\(dis(k,x)>dis(k,q)\),根據定義,將直徑端點換成\(x\)顯然可以使直徑更長,這與“直徑是最長鏈”相違背
\((2)k\)不在直徑上
\(\because b+d>d+c+a\)
\(\therefore b>c+a\)
\(\therefore b+c>a\)
可以將直徑的\(a\)段換成\(b+c\)更優
這與“直徑是最長鏈”相違背
綜上,我們所說的方法是正確的
樹的重心
概念:以樹的重心為整棵樹的根時,它的最大子樹最小(也就是刪除該點后最大聯通塊最小)
用\(siz[i]\)表示以\(i\)為根的子樹的總大小(包括根)
用\(mson[i]\)表示以\(i\)的最大子樹的大小
隨便指定一個點\(dfs\)一下順便維護上面兩個值。
\(n-siz[i]\)就是刪去\(i\)后上方聯通塊大小,只需要跟\(mson[i]\)取個\(max\),就是以該節點為根時最大子樹的大小,然后更新答案即可
ans=INF;
void dfs(int u,int fa)
{
siz[u]=1,mson[u]=0;//注意初始化為0,因為有可能沒有子樹
for(int i=head[u];i;i=e[i].nxt)
{
int ev=e[i].v;
if(ev==fa) continue;
dfs(ev,u);
siz[u]+=siz[ev];
mson[u]=max(mson[u],siz[ev]);
}
ans=min(ans,max(n-siz[u],mson[u]));
}
樹的中心
概念:以樹的中心為整棵樹的根時,從該根到每個葉子節點的最長路徑最短
也有兩種方法
\(1.\)樹形\(DP\)
我們需要維護每個點到所有葉子節點的最長距離
前面已經知道了怎么維護每個節點到它的子樹中的葉子節點的最長距離和次長距離,考慮怎么維護這個點向上的最遠距離
\(c1[i]\)表示\(d1[i]\)從哪個點更新,\(c2[i]\)表示\(d2[i]\)從哪個點更新,用\(up[i]\)表示向上的最遠距離。
再用一開始指定的點做一次\(DFS\),這次是從根到葉子節點狀態轉移。
對於每一個點,假設它的父親的最長鏈,也就是\(d1[fa[u]]\)不是從它更新來的,那么\(up[u]=max(up[fa[u]],d1[fa[u]])+dis[fa[u]][u]\)
如果的父親的最長鏈是從它更新來的,那次長鏈一定不是從它更新來的,可以看看前面的定義,兩條鏈沒有交集,所以
\(up[u]=max(up[fa[u]],d2[fa[u]])+dis[fa[u]][u]\)
最后這樣更新答案
ans=min(ans,max(up[i],d1[i]));
直接上完整代碼吧
#include<cstdio>
#include<iostream>
#define re register
#define maxn 100010
using namespace std;
inline int read()
{
int x=0,f=1; char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
struct Edge{
int v,w,nxt;
}e[maxn<<2];
int ans,x,y,pos,z;
int n,head[maxn],cnt,d1[maxn],up[maxn],d2[maxn],c1[maxn],c2[maxn];
inline void add(int u,int v,int w)
{
e[++cnt].v=v;
e[cnt].w=w;
e[cnt].nxt=head[u];
head[u]=cnt;
}
void dfs1(int u,int fa)
{
for(int i=head[u];i;i=e[i].nxt)
{
int ev=e[i].v;
if(ev==fa) continue;
dfs1(ev,u);
if(d1[ev]+e[i].w>d1[u])
d2[u]=d1[u],c2[u]=c1[u],d1[u]=d1[ev]+e[i].w,c1[u]=ev;
else if(d1[ev]+e[i].w>d2[u])
d2[u]=d1[ev]+e[i].w,c2[u]=ev;
}
}
void dfs2(int u,int fa)
{
for(int i=head[u];i;i=e[i].nxt)
{
int ev=e[i].v;
if(ev==fa) continue;
if(c1[u]!=ev) up[ev]=max(d1[u],up[u])+e[i].w;//不是從它更新來的
else up[ev]=max(d2[u],up[u])+e[i].w;
dfs2(ev,u);
}
}
int main()
{
n=read();
for(re int i=1;i<n;++i)
{
x=read(),y=read(),z=read();
add(x,y,z),add(y,x,z);
}
dfs1(1,0);
dfs2(1,0);
ans=0x3f3f3f3f;
for(re int i=1;i<=n;++i)
{
if(max(up[i],d1[i])<ans) ans=max(up[i],d1[i]),pos=i;
}
printf("%d %d",pos,ans);//pos表示中心位置
return 0;
}
\(2.\)簡單\(DFS/BFS\)
樹的中心一定在樹的直徑上,且趨於中點
這個是比較顯然的,如果不在直徑上,它的最遠距離只會更遠
因此我們在找出直徑的同時,對於直徑的兩個端點\(pos1,pos2\),分別求到每個點的距離\(d1[i],d2[i]\)
最后對於每個點更新即可
ans=min(ans,max(d1[i],d2[i]));
完整代碼
#include<cstdio>
#include<iostream>
#define re register
#define maxn 100010
using namespace std;
inline int read()
{
int x=0,f=1; char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
struct Edge{
int v,w,nxt;
}e[maxn<<2];
int x,y,z;
int pos1,pos2,d[maxn],d1[maxn],d2[maxn];
int n,tmp1,tmp2,tmp3,ans,pos,cnt,head[maxn];
inline void add(int u,int v,int w)
{
e[++cnt].v=v;
e[cnt].w=w;
e[cnt].nxt=head[u];
head[u]=cnt;
}
void dfs1(int u,int fa,int dis)
{
for(int i=head[u];i;i=e[i].nxt)
{
int ev=e[i].v;
if(ev==fa) continue;
dfs1(ev,u,dis+e[i].w);
}
d[u]=dis;
if(dis>tmp2) tmp2=dis,tmp1=u;
}
int main()
{
n=read();
for(re int i=1;i<n;++i)
{
x=read(),y=read(),z=read();
add(x,y,z);
add(y,x,z);
}
dfs1(1,0,0);
pos1=tmp1;
tmp2=0,tmp1=0;
dfs1(pos1,0,0);
pos2=tmp1;
tmp2=0,tmp1=0;
//找到直徑了
for(re int i=1;i<=n;++i) d1[i]=d[i];
dfs1(pos2,0,0);
for(re int i=1;i<=n;++i) d2[i]=d[i];
ans=0x3f3f3f3f;
for(re int i=1;i<=n;++i)
{
if(ans>max(d1[i],d2[i]))
ans=max(d1[i],d2[i]),pos=i;
}
printf("%d %d",pos,ans);
return 0;
}
最后附一個樹的數據生成器
#include<cstdlib>
#include<iostream>
#include<ctime>
#include<cstdio>
#include<algorithm>
#define re register
using namespace std;
int n,q,qx,qy,w;
int x[10010],y[10010],z[10010];
int a[10010],fa[10010];
int cnt2,cnt;
struct Edge{
int u,v,w;
}e[10010];
int find(int x)
{
return fa[x]==x?x:find(fa[x]);
}
int flag;
int main()
{
srand(time(0));
n=rand()%10+1;
printf("%d\n",n);
for(re int i=1;i<=n;++i)
{
for(re int j=i+1;j<=n;++j)
{
x[++cnt]=i;
y[cnt]=j;
z[cnt]=rand()%100+1;
}
}
for(re int i=1;i<=cnt;++i) a[i]=i,fa[i]=i;
random_shuffle(a+1,a+cnt+1);
for(re int i=1;i<=cnt;++i)
{
int pos=a[i];
int eu=find(x[pos]),ev=find(y[pos]);
if(eu==ev) continue;
fa[ev]=eu;
e[++cnt2].u=x[pos],e[cnt2].v=y[pos],e[cnt2].w=z[pos];
if(cnt2==n-1) break;
}
for(re int i=1;i<=cnt2;++i)
{
printf("%d %d %d\n",e[i].u,e[i].v,e[i].w);
}
return 0;
}