树的直径
1树的直径
就是树上的一条链,满足这条链上权值最大。
2求解
2.1两次dfs
这种方法不适用于负边权,但是容易知道树直径的起始节点和末节点,也容易知道链的路径。
关于两次dfs不适用于负边权的证明放在引用处,感兴趣的读者可以自行观看。
代码:
inline int dfs(int k,int fa,int &t){
int sum=0,tt;
for(int x=head[k];x;x=li[x].next){
int to=li[x].to,w=li[x].w;
if(to==fa) continue;
int val=dfs(to,k,tt)+w;
if(sum<val){
sum=val;
t=tt;
}
}
if(!sum){
t=k;
return 0;
}
return sum;
}
2.2树形dp
可以处理负边权的情况,但是我的打法比较难于处理链的路径。
inline void solve(int k,int fa){
bool in=0;
for(int x=head[k];x;x=li[x].next){
int to=li[x].to,w=li[x].w;
if(to==fa) continue;
in=1;
solve(to,k);
ans=Max(ans,d[k]+d[to]+w);
d[k]=Max(d[k],d[to]+w);
}
ans=Max(ans,d[k]);
if(!in) d[k]=0;
}
d数组提前赋值为\(-INF\)。
3例题
3.1模板题
直接上代码即可:
树形dp:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<sstream>
#include<queue>
#include<map>
#include<vector>
#include<set>
#include<deque>
#include<cstdlib>
#include<ctime>
#define dd double
#define ld long double
#define ll long long
#define ull unsigned long long
#define N 500100
#define M number
using namespace std;
const int INF=0x3f3f3f3f;
inline ll read(){
ll 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;
}
inline int Max(int a,int b){
return a>b?a:b;
}
struct edge{
int to,next,w;
inline void intt(int to_,int ne_,int w_){
to=to_;next=ne_;w=w_;
}
};
edge li[N*2];
int head[N],tail;
inline void add(int from,int to,int w){
li[++tail].intt(to,head[from],w);
head[from]=tail;
}
int d[N],n,ans=-INF;//0 max 1 cimax
inline void solve(int k,int fa){
bool in=0;
for(int x=head[k];x;x=li[x].next){
int to=li[x].to,w=li[x].w;
if(to==fa) continue;
in=1;
solve(to,k);
ans=Max(ans,d[k]+d[to]+w);
d[k]=Max(d[k],d[to]+w);
}
ans=Max(ans,d[k]);
if(!in) d[k]=0;
}
int main(){
n=read();
for(int i=1;i<=n-1;i++){
int from=read(),to=read(),w=read();
add(from,to,w);add(to,from,w);
}
memset(d,-INF,sizeof(d));
solve(1,-1);
printf("%d\n",ans);
return 0;
}
两次dfs:(拿不了全分,该题有负边权)
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<sstream>
#include<queue>
#include<map>
#include<vector>
#include<set>
#include<deque>
#include<cstdlib>
#include<ctime>
#define dd double
#define ld long double
#define ll long long
#define ull unsigned long long
#define N 1000001
#define M number
using namespace std;
const int INF=0x3f3f3f3f;
inline ll read(){
ll 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 to,next,w;
inline void intt(int to_,int ne_,int w_){
to=to_;next=ne_;w=w_;
}
};
edge li[N];
int head[N],tail;
inline void add(int from,int to,int w){
li[++tail].intt(to,head[from],w);
head[from]=tail;
}
inline int dfs(int k,int fa,int &t){
int sum=0,tt;
for(int x=head[k];x;x=li[x].next){
int to=li[x].to,w=li[x].w;
if(to==fa) continue;
int val=dfs(to,k,tt)+w;
if(sum<val){
sum=val;
t=tt;
}
}
if(!sum){
t=k;
return 0;
}
return sum;
}
int n,x,xx,ans;
int main(){
n=read();
for(int i=1;i<=n-1;i++){
int from=read(),to=read(),w=read();
add(from,to,w);add(to,from,w);
}
dfs(1,-1,x);
// printf("here\n");
ans=dfs(x,-1,xx);
printf("%d\n",ans);
return 0;
}
3.2P4408 [NOI2003] 逃学的小孩
利用贪心算法。
因为这张图是一颗树,所以很自然的想到树的直径。
我们考虑一下我们两次dfs算法的流程,
我们从任意一点出发,到最远的点去,
在从那个点出发,再到最远的点去。
这两次dfs所经过路径长,分别记为\(L_1,L_2\),已知\(L_2\)为树的直径。
如果我们第一次dfs不走该路径,设我们现在第一次走的路径长为\(L_3\),
根据\(L_1\)的定义,我们显然有\(L_3\le L_1\),在这一次的基础上,进行第二次dfs,设这一次第二次dfs走的路径长为\(L_4\),显然有\(L_4\leq L_3\),所以\(L_3+L_4\leq L_1+L_2\)
综上所述,无论从哪里出发,第一次dfs所走路径一定到达了最远点,第二次dfs所走路径一定为树的直径。
所以我们两次dfs把树的直径求出来,然后计算到树的直径的端点的最远点是哪里,即为所求答案。
无论是哪一条树的直径都可以,因为树的直径有以下性质:
- 任意两条树的直径必定在中点相交(中点可能不是节点)
代码:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<sstream>
#include<queue>
#include<map>
#include<vector>
#include<set>
#include<deque>
#include<cstdlib>
#include<ctime>
#define dd double
#define ld long double
#define int long long
#define ll long long
#define ull unsigned long long
#define N 200010
#define M 400020
using namespace std;
const int INF=0x3f3f3f3f;
inline int Max(int a,int b){
return a>b?a:b;
}
inline int Min(int a,int b){
return a<b?a:b;
}
inline ll read(){
ll 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 to,next,w;
inline void intt(int to_,int ne_,int w_){
to=to_;next=ne_;w=w_;
}
};
edge li[M];
int head[N],tail;
inline void add(int from,int to,int w){
li[++tail].intt(to,head[from],w);
head[from]=tail;
}
int n,m,s,ss,f[N][2],ans,maxx;
inline int dfs(int k,int fa,int &t){
int sum=0,tt;
for(int x=head[k];x;x=li[x].next){
int to=li[x].to,w=li[x].w;
if(to==fa) continue;
int val=dfs(to,k,tt)+w;
if(sum<val){
sum=val;
t=tt;
}
}
if(!sum){
t=k;
return 0;
}
return sum;
}
queue<int> q;bool vis[N];
inline void bfs(int k,int op){
while(q.size()) q.pop();
memset(vis,0,sizeof(vis));
q.push(k);vis[k]=1;
while(q.size()){
int top=q.front();q.pop();
for(int x=head[top];x;x=li[x].next){
int to=li[x].to,w=li[x].w;
if(vis[to]) continue;
f[to][op]=f[top][op]+w;
q.push(to);vis[to]=1;
}
}
}
signed main(){
n=read();m=read();
for(int i=1;i<=m;i++){
int from=read(),to=read(),w=read();
add(from,to,w);add(to,from,w);
}
dfs(1,-1,s);
ans=dfs(s,-1,ss);
bfs(s,0);
bfs(ss,1);
for(int i=1;i<=n;i++){
maxx=Max(maxx,Min(f[i][0],f[i][1]));
}
ans+=maxx;
printf("%lld",ans);
return 0;
}
3.3P3629
我们先考虑k=1的情况
容易发现,当我们添加一条边时,与这条边组成环的树边只用经过一次。
所以我们直接求树的直径即可。
考虑k=2
如果两个环没有相交的部分,则和k=1的情况一样。
如果有相交的部分,发现相交的部分又重新变回了需要经过两次。
一个很妙的处理是在第一次求完树的直径后把树的直径上的边全职全部取反(1变到-1)然后在跑一次树的直径,
这样在减得时候,相交的部分又重新加了回来。
代码:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<sstream>
#include<queue>
#include<map>
#include<vector>
#include<set>
#include<deque>
#include<cstdlib>
#include<ctime>
#define dd double
#define ld long double
#define ll long long
#define ull unsigned long long
#define N 100100
#define M number
using namespace std;
const int INF=0x3f3f3f3f;
inline int Max(int a,int b){
return a>b?a:b;
}
inline ll read(){
ll 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 to,next,w;
inline void intt(int to_,int ne_,int w_){
to=to_;next=ne_;w=w_;
}
};
edge li[N*2];
int head[N],tail=1;
inline void add(int from,int to,int w){
li[++tail].intt(to,head[from],w);
head[from]=tail;
}
int d[N],ans1,ans2,n,k,ans=-INF,pree[N],s,ss;
inline int dfs(int k,int fa,int &t,bool op){
int sum=0,tt;
for(int x=head[k];x;x=li[x].next){
int to=li[x].to,w=li[x].w;
if(to==fa) continue;
int val=dfs(to,k,tt,op)+w;
if(sum<val){
sum=val;
t=tt;
if(op) pree[k]=x;
}
}
if(!sum){
t=k;
return 0;
}
return sum;
}
inline void solve(int k,int fa){
bool in=0;
for(int x=head[k];x;x=li[x].next){
int to=li[x].to,w=li[x].w;
if(to==fa) continue;
in=1;
solve(to,k);
ans=Max(ans,d[k]+d[to]+w);
d[k]=Max(d[k],d[to]+w);
}
ans=Max(ans,d[k]);
if(!in) d[k]=0;
}
//inline void print(int k){
// printf("%d\n",k);
// int bian=pree[k];
// k=li[bian].to;
// if(k) print(k);
//}
inline void update(int k){
// printf("%d ",k);
int bian=pree[k];
li[bian].w=-1;
li[bian^1].w=-1;
// printf("%d %d\n",bian,bian^1);
k=li[bian].to;
if(k) update(k);
}
int main(){
n=read();k=read();
for(int i=1;i<=n-1;i++){
int from=read(),to=read();
add(from,to,1);add(to,from,1);
}
dfs(1,-1,s,0);
// printf("%d\n",s);
ans=dfs(s,-1,ss,1);
// printf("%d\n",ans);
// print(s);
if(k==1){
printf("%d",2*(n-1)-ans+1);
return 0;
}
ans1=ans;
ans=-INF;
update(s);
memset(d,-INF,sizeof(d));
solve(1,-1);
ans2=ans;
// printf("%d\n",ans2);
// printf("%d\n",2*(n-1)-ans1+1);
if(ans2<0) ans2=0;
printf("%d",2*n-ans1-ans2);
return 0;
}
引用
- 两次dfs不适用负边权证明
- 算法竞赛进阶指南