虽然有很多Dalao发过很多非常详细的题解,但是本蒟蒻还是(凭着初生牛犊不怕虎的精神)来发一篇题解。
因为最近正在复习原来学习的图论,正巧(一点也不巧)看到了这道题,然后就……点了进来。
其实刚看到这道题的时候,还是有一点懵的(当然,这是一道图论是没得跑的)。
然后,我就从头理了一下思路。
首先:
1、该图上的所有道路都要到达。
2、所有操作都是从一号点开始,一号点结束。
3、要使得巡逻距离较短。
第一步
我们先考虑不加边的情况
从一号点出发,要把每个边遍历一遍再回到1号点,会恰好经过每条边2次,总路线哦不,是经过的路线总长为2(n-1)。
----
第二步
我们来考虑 k==1 的情况。
建立一条新边,因为每一次新建的边都要。
恰好 经过一次
因为这是一棵树,所以当我们加上一条边时就构成了一个环,而这个环便可以使这个图在遍历时可以减少重复走的边。
如图(不是很非常丑):
因为这个环所带来的重复路径的减少,所以我们在构建环时要找到此时树中最长的链进行构造,这样的话我们就可以满足题上的最短解,这个时候我们来推一下公式:
2(n-1)-L+1
如果你已经想到这里,那么恭喜你,你已经成功地拿到了30分。下面我们来看如何AC这道题。
***
第三步
第一条路建完以后,我们来考虑第二条路。当第二条新路(u,v)建立之后,又会形成一个环。当连个环不重叠的时候,答案会一直缩小。但是如果有重叠部分,就会有一部分的路不会被巡逻到,所以我们就只好将车重新巡逻这条边,最后返回。这步操作最终又使那条边重复经过了一次。
具体见图:
这样的话,我们就得到了最短的路径。
总结一下具体的步骤:
1、先在原树上找到该树的直径,记为L1,然后将边权改为-1(后面会解释为什么是-1)。
2、在取反边权后再次寻找该树的直径记为L2。
3、计算答案。
公式
由前面的讲解可推知:
ans=2(n-1)-(L1-1)-(L2-1)
即
ans=2n-L1-L2
时间复杂度的话就十分的直观为O(n)。
***
前面的思路就讲解到这里了,下面我们来讲一讲代码实现。
首先
这道题的一个最直观的考点——树的直径
所以下面就介绍一下树的直径这个考点。
树的直径简单来说就是树中最长的链,下面将有两种O(n)的方法来求树的直径。
背景:假设树以N个节点N-1条边以无向图的形式给出并以邻接表的形式给出。
第一种:树形DP求树的直径
我们用d[x]表示距离,f[x]则表示任意两点之间的距离。
代码实现:
void dp(int x){
v[x]=1;
for(int i=head[x];i;i=e[i].next){
int y=e[i].to;
if(v[y])continue;
dp(y);
ans=max(ans,dis[x]+dis[y]+e[i].w);
dis[x]=max(dis[x],dis[y]+e[i].w);
}
}
用树形dp来求解的话,一个十分明显的优点就是可以处理负权制的问题
***
第二种:两次BFS求树的直径
通过两次BFS求出直径,更易算出直径上的直径上的具体节点。
步骤 :
①从树上任意一点出发P,找到与其距离最大的点M
②从点M出发,找到与其距离最大的点N
③MN即为树的直径
下面是具体证明(证明过程摘自老师ppt)。
反证法:假设M不是直径的一个端点,AB是树的直径。
① 如果P是直径上的点,如图,PM>PB则AP+PM>AP+PB=AB这与AB是直径矛盾。
②-1 若P不在直径上:P到M路径与A到B路径有公共结点TPT+TM>PT+TB,则TM>TB,故AT+TM>AT+TB=AB,矛盾
②-2 P到M的路径与A到B的路径无公共结点PC+CM>PC+CD+BD,则CM>CD+BD,CM+CD>BD故CM+CD+AD>BD+AD=AB,矛盾。
这样两边BFS的正确性就证明完毕。
代码:
void bfs(int x,int ck){
memset(dis,0,sizeof(dis));
memset(vis,0,sizeof(vis));
queue<int >q;
q.push(x);
vis[x]=1;
while(!q.empty()){
int now=q.front();
q.pop();
for(int i=head[now];i;i=e[i].next){
int y=e[i].to;
if(!vis[y]){
vis[y]=1;
dis[y]=dis[now]+e[i].w;
q.push(y);
if(ck)f[y]=now;//记录父节点,修改时会用
}
}
}
for(int i=1;i<=n;i++){
if(ans<dis[i])ans=dis[i],point=i;
}
}
两遍BFS求树的直径的优点很明显,但是缺点也很突出,就是无法处理负权值。
所以我们在处理过第一次的直径后将权值赋为了-1,就是这个原因。
下面就是这道题的AC代码了
#include<bits/stdc++.h>
using namespace std;
template<typename type>
void scan(type &x){
type f=1;x=0;char s=getchar();
while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
x*=f;
}
const int N=1e5+7;
struct node{
int to,w,next;
}e[N*2];
int head[N],cnt;
void add(int a,int b,int c){
e[++cnt].to=b;
e[cnt].next=head[a];
head[a]=cnt;
e[cnt].w=1;
}
int dis[N],vis[N],l1,l2,n,k,point,f[N],d[N],v[N];
int ans=0;
void bfs(int x,int ck){
memset(dis,0,sizeof(dis));
memset(vis,0,sizeof(vis));
queue<int >q;
q.push(x);
vis[x]=1;
while(!q.empty()){
int now=q.front();
q.pop();
for(int i=head[now];i;i=e[i].next){
int y=e[i].to;
if(!vis[y]){
vis[y]=1;
dis[y]=dis[now]+e[i].w;
q.push(y);
if(ck)f[y]=now;//记录父节点,修改时会用
}
}
}
for(int i=1;i<=n;i++){
if(ans<dis[i])ans=dis[i],point=i;
}
}
void change(int a){//修改边权值
while(f[a]){
int fa=f[a];
for(int i=head[fa];i;i=e[i].next){
// e[i].w=-1;
if(e[i].to==a){
// e[i].w=e[i^1].w=-1;
e[i].w=-1;
break;
}
}
for(int i=head[a];i;i=e[i].next){
if(e[i].to==fa){
e[i].w=-1;
break;
}
}
a=fa;
}
}
void dp(int x){
v[x]=1;
for(int i=head[x];i;i=e[i].next){
int y=e[i].to;
if(v[y])continue;
dp(y);
ans=max(ans,dis[x]+dis[y]+e[i].w);
dis[x]=max(dis[x],dis[y]+e[i].w);
}
}
int main(){
scan(n);scan(k);
// scanf("%d%d",&n,&k);
for(int i=1;i<n;i++){
int a;int b;
scan(a);scan(b);
// scanf("%d%d",&a,&b);
add(a,b,1);
add(b,a,1);
}
if(k==1){
bfs(1,0);
bfs(point,0);
printf("%d",2*n-dis[point]-1);
}else{
bfs(1,0);
bfs(point,1);
l1=dis[point];
change(point);
memset(dis,0,sizeof(dis));
memset(vis,0,sizeof(vis));
ans=0;
dp(1);
// printf("%d\n",l1);
// printf("%d\n",ans);
printf("%d",2*n-ans-l1);
}
return 0;
}