今天梁老给我们上的课
好像比zgs讲的要清楚一些
树是一个没有环的图
无根树是一个没有环没有根节点的图(所以你可以随便令一个点来做它的根节点)
我想砍树向第一届C·S·P致敬
树的直径
所谓树的直径,就是树中找到两点,使他们的连线最长
搜树的直径:
找到任一点P
求得距离P最远的点Q
再求得离Q最远的点W
于是QW即为树的直径
代码实现
#include<bits/stdc++.h>
using namespace std;
#define in Read()
#define re register
const int NNN =(int)1e5+10;
int n;
int tot,first[NNN],next[NNN<<1],aim[NNN<<1];
int ans,t,d[NNN];
inline int in{
int i=0,f=1;char ch;
while((ch>'9'||ch<'0')&&ch!='-')ch=getchar();
if(ch=='-')ch=getchar(),f=-1;
while(ch>='0'&&ch<='9')i=(i<<1)+(i<<3)+ch-48,ch=getchar();
return i*f;
}
inline void Add(int u,int v){
tot++;
next[tot]=first[u];
first[u]=tot;
aim[tot]=v;
return ;
}
inline void Dfs(int x,int father){
if(d[x]>ans){
ans=d[x];
t=x;
}
for(re int e=first[x];e;e=next[e]){
if(aim[e]==father)continue;
d[aim[e]]=d[x]+1;
Dfs(aim[e],x);
}
return ;
}
int main(){
n=in;
for(re int i=1;i<n;i++){
int u=in,v=in;
Add(u,v);
Add(v,u);
}
Dfs(1,0);
d[t]=0;
ans=0;
Dfs(t,0);
printf("%d\n",ans);
return 0;
}
如果只求长度:
用树形DP
对于每一个根节点
都找到它第1与第2长的根节点到叶结点的距离
打个擂台就完了
更新的方法:
if(d1[e[son].to]+1>d1[x])d2[x]=d1[x],d1[x]=d1[e[son].to]+1;
else d2[x]=max(d1[e[son].to]+1,d2[x]);
代码实现
#include<bits/stdc++.h>
using namespace std;
#define re register
#define in Read()
const int NNN =(int)1e5+10;
int n,tot;
struct st{
int to,next;
}e[NNN*2];
int d1[NNN],d2[NNN],first[NNN];
int len;
void Add(int u,int v){
tot++;
e[tot].next=first[u];
first[u]=tot;
e[tot].to=v;
return ;
}
int in{
int i=0,f=1;char ch;
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')ch=getchar(),f=-1;
while(ch>='0'&&ch<='9')i=(i<<1)+(i<<3)+ch-48,ch=getchar();
return i*f;
}
void Dfs(int x,int father){
for(re int son=first[x];son;son=e[son].next){
if(e[son].to==father)continue;
Dfs(e[son].to,x);
if(d1[e[son].to]+1>d1[x])d2[x]=d1[x],d1[x]=d1[e[son].to]+1;
else d2[x]=max(d1[e[son].to]+1,d2[x]);
}
len=max(len,d1[x]+d2[x]);
}
int main(){
n=in;
for(re int i=1;i<n;i++){
int u=in,v=in;
Add(u,v);
Add(v,u);
}
Dfs(1,0);
printf("%d\n",len);
return 0;
}
思考:求树的中心
树的中心:即求树中的点对最远距离最近
很明显,中心是直径的中点
练习
luoguP4408
sol
“可以保证,任两个居住点间有且仅有一条通路” 说明街道与居住点组成树
题意要求:求
由于A,B,C是不知道的点
那么如果不满足AC>BC,将A,B调换之后即满足
于是有
由感性认识 (证不来) 得到:AB是直径,
是任一点与直径端点相连的最短距离中最长的
要得到直径的端点,只能用两次DFS解决
代码实现
#include<bits/stdc++.h>
using namespace std;
#define in Read()
#define re register
#define int long long
const int NNN =(int)2e5+10;
int n,m;
int tot,first[NNN],next[NNN<<1],aim[NNN<<1],wei[NNN<<1];
int d[NNN][3],st,A,B,ans,len;//求到了端点,就把它存进A或B中
inline int in{
int i=0,f=1;char ch;
while((ch>'9'||ch<'0')&&ch!='-')ch=getchar();
if(ch=='-')ch=getchar(),f=-1;
while(ch>='0'&&ch<='9')i=(i<<1)+(i<<3)+ch-48,ch=getchar();
return i*f;
}
inline void Add(int u,int v,int w){
tot++;
next[tot]=first[u];
first[u]=tot;
aim[tot]=v;
wei[tot]=w;
return ;
}
inline void Dfs(int x,int father,int p){
if(d[x][p]>ans){
ans=d[x][p];
st=x;
}
for(re int e=first[x];e;e=next[e]){
if(aim[e]==father)continue;
d[aim[e]][p]=d[x][p]+wei[e];
Dfs(aim[e],x,p);
}
return ;
}
signed main(){
n=in,m=in;
for(re int i=1;i<=m;i++){
int u=in,v=in,w=in;
Add(u,v,w);
Add(v,u,w);
}
//两次DFS求端点和直径长
Dfs(1,0,0);
A=st;
d[A][1]=0;
ans=0;
Dfs(A,0,1);
B=st;
//刚刚DFS的时候已经求出了每个点到A的距离,只需要再求出B,然后比较就得到min{AC,BC}
Dfs(B,0,2);
for(re int i=1;i<=n;i++){
int Antimony=min(d[i][1],d[i][2]);
len=max(len,Antimony);
// printf("%lld %lld\n",Antimony,len);
}
// printf("%lld %lld\n",A,B);
// for(re int i=1;i<=n;i++)
// printf("%lld %lld %lld\n",i,d[i][1],d[i][2]);
printf("%lld\n",ans+len);
return 0;
}
这题太坑了
必须开long long——
稍微一不注意就炸int了
luoguP3629
“在一个地区中有 n 个村庄,编号为 1, 2, …, n. 有 n – 1 条道路连接着这些村庄,每条道路刚好连接两个村庄,从任何一个村庄,都可以通过这些道路到达其 他任一个村庄。”——无根树,随便取顶点
这道题劲道太大了,如果一来就看加几条边的话,多半做不起
那·由易至难♂?
请看这个图(树被我砍了,而且“树”这个词在另一个人的字典里面消失了)
如果只加一条边
那么会形成一个环
原来每条边要经过2遍,一共2n遍
加上环,环上的每条边都只用经过1遍,一共只需经过m遍(树上有m条边)
于是现在只需要走2n-m条边
如果再加一条边
会出现两种情况——
case1
与刚刚加成的环有公共边
一共形成了3个环
要走只能走一个,另外的跟不加环一样要走2遍
case2
没有形成公共边
于是又形成了一个环
又减少了m次
这种情况很简单,再找一个环出来就行了
究其因,要减少次数,就要形成没有公共边(可能有公共点)的环
tarjan派上用场(然而某巨巨又在给本蒟蒻讲基环树-听不懂听不懂听不懂我能想到tarjan我已经尽力了!!!)
好吧实际上不需要tarjan那么复杂(而且tarjan还是错的,因为有重边的情况可能是可以优化的)
稍微数学推导——
设树共有n个点,直径上长为d
不加边:2n-2
加一边:
其中d-1为直径上少走的边数,+1是经过加的一条边
所以只需要减去直径长+1就可以了
加两边:画图可知,连了边之后公共边的部分跟不连边要花费一样的时间
为什么呢?
完美
直径上的边权全部改为负的
一直这样更新ans
直到ans’(更新之后的ans)>ans(原ans)(如果可以加无穷多条边)
代码实现(必须用DFS,要求直径上每个点)(A70,A码咕掉了)
#include<bits/stdc++.h>
using namespace std;
#define in Read()
#define re register
const int NNN =(int)1e5+10;
int n,k;
int tot,first[NNN],next[NNN<<1],wei[NNN<<1],aim[NNN<<1],near[NNN<<1];
int st,d[NNN],ans,Antimony,tmp;
bool I_have_found_D;
int fr[NNN];//记录直径上的点
inline int in{
int i=0,f=1;char ch;
while((ch>'9'||ch<'0')&&ch!='-')ch=getchar();
if(ch=='-')ch=getchar(),f=-1;
while(ch>='0'&&ch<='9')i=(i<<1)+(i<<3)+ch-48,ch=getchar();
return i*f;
}
inline void Add(int u,int v){
tot++;
next[tot]=first[u];
first[u]=tot;
aim[tot]=v;
wei[tot]=1;
return ;
}
inline void Dfs(int x,int father){
if(d[x]>=ans){
ans=d[x];
st=x;
}
for(re int e=first[x];e;e=next[e]){
if(aim[e]==father)continue;
d[aim[e]]=d[x]+wei[e];
Dfs(aim[e],x);
}
return ;
}
inline void changewei(int now,int father,int target){
if(I_have_found_D)return ;
for(re int e=first[now];e;e=next[e]){
if(aim[e]==father)continue;
if(I_have_found_D)return ;
if(aim[e]==target){
I_have_found_D=true;
fr[now]=aim[e];
return ;
}
fr[now]=aim[e];
changewei(aim[e],now,target);
if(I_have_found_D==true)return ;
}
return ;
}
int main(){
n=in,k=in;
for(re int i=1;i<n;i++){
int u=in,v=in;
Add(u,v);
Add(v,u);
near[tot]=tot-1;
near[tot-1]=tot;
}
//k==1
st=1;
d[1]=0;
ans=0;
Dfs(1,0);
tmp=st;
d[st]=0;
ans=0;
Dfs(st,0);
Antimony=ans;
if(k==1){
printf("%d\n",((n-1)<<1)-Antimony+1);
return 0;
}
//k==2
// printf("%d %d\n",st,tmp);
// printf("\n");
changewei(st,0,tmp);
// for(re int i=st;i!=tmp;i=fr[i])
// printf("%d %d\n",i,fr[i]);
//
// for(re int i=1;i<=n;i++){
// printf("%d ",i);
// for(re int e=first[i];e;e=next[e]){
// if(wei[e]<0)printf("-");
// printf("%d ",aim[e]);
// }
// printf("\n");
// }
//
for(re int i=st;i!=tmp;i=fr[i])
for(re int e=first[i];e;e=next[e])
if(aim[e]==fr[i])
wei[e]=-1,wei[near[e]]=-1;
//
// for(re int i=1;i<=n;i++){
// printf("%d ",i);
// for(re int e=first[i];e;e=next[e]){
// if(wei[e]<0)printf("-");
// printf("%d ",aim[e]);
// }
// printf("\n");
// }
st=1;
d[st]=0;
ans=0;
Dfs(1,0);
tmp=st;
d[st]=0;
ans=0;
Dfs(st,0);
// printf("%d\n",ans);
printf("%d\n",(n<<1)-Antimony-ans);
return 0;
}
第二天早上才发现
且
关键在于处理改边权(无向图)
两种方法:
1 利用数学知识,发现建邻接表的时候两条反向的边是相邻的
2 开数组再存一个元素
经验:用邻接表的思想求直径上的点(推广到任意两点之间)
消防 咕
不管什么我只知道这条道路可能不在树的直径上面