P3629 [APIO2010]巡逻-树的直径

在一个地区中有 n 个村庄,编号为 1, 2, ..., n。有 n – 1 条道路连接着这些村 庄,每条道路刚好连接两个村庄,从任何一个村庄,都可以通过这些道路到达其 他任一个村庄。每条道路的长度均为 1 个单位。 为保证该地区的安全,巡警车每天要到所有的道路上巡逻。警察局设在编号 为 1 的村庄里,每天巡警车总是从警察局出发,最终又回到警察局。 下图表示一个有 8 个村庄的地区,其中村庄用圆表示(其中村庄 1 用黑色的 圆表示),道路是连接这些圆的线段。为了遍历所有的道路,巡警车需要走的距 离为 14 个单位,每条道路都需要经过两次。

P3629 [APIO2010]巡逻-树的直径_第1张图片

为了减少总的巡逻距离,该地区准备在这些村庄之间建立 K 条新的道路, 每条新道路可以连接任意两个村庄。两条新道路可以在同一个村庄会合或结束 (见下面的图例(c))。 一条新道路甚至可以是一个环,即,其两端连接到同一 个村庄。 由于资金有限,K 只能是 1 或 2。同时,为了不浪费资金,每天巡警车必须 经过新建的道路正好一次。 下图给出了一些建立新道路的例子:

P3629 [APIO2010]巡逻-树的直径_第2张图片

在(a)中,新建了一条道路,总的距离是 11。在(b)中,新建了两条道路,总 的巡逻距离是 10。在(c)中,新建了两条道路,但由于巡警车要经过每条新道路 正好一次,总的距离变为了 15。 试编写一个程序,读取村庄间道路的信息和需要新建的道路数,计算出最佳 的新建道路的方案使得总的巡逻距离最小,并输出这个最小的巡逻距离。

https://www.luogu.org/problemnew/show/P3629

k==1

当k==1时,发现,只要不zz一般地从自己连到自己(造的路必须通过),都是可以帮警察省下路程的,所以我们不妨考虑一下贪心地选择,即选择树上距离最长的两点,因为不管我们选择了那两个点,省下的距离都是两点之间的距离 - 1(走造的那条路还要 1) 那么我们只要找到距离最大的两个点就可以省下最长的路程这就引出了我们的主角——树的直径

树的直径

什么是树的直径呢?其实就是树上距离相差最大的两个点之间的路径,概念非常好理解,代码也不长

怎么树的直径

  1. 两次dfs/bfs
  2. 树形dp

两种方法各有优缺点将在先问中讲到

dfs/bfs求树的直径

其实就是在树上随机选一个点,对他进行一次dfs/bfs求出距离它距离最远的一个点A,然后再用那一个点dfs/bfs回来找到一个离A点距离最远的B点,AB之间的路径即为树的直径

优点:可以记录直径的起点,重点,再配合一个dfs可以求出直径上的每一个点

缺点:遇到负边权的树就GG

树上dp求树的直径

通过树形dp的方式,通过求出差不多每个点之间的距离,求最大值,听上去比较难理解,但学过树形dp的同学应该都知道码量比较小

优点:可以处理负边权的树

缺点:真的只能求一个树的直径的长度,其他的求不出

两种方法互相补充,正是这题的优秀之处,它同时考到了两种算法

然后当k==1时,就可以省下树的直径-1的路啦(显然这是省的最多的),由于本来要走的路程是(N-1)<<1(每条路走两次),我们当k==1时的答案就是

2*(N-1)-maxn+1//maxn为直径长度

k==2时

这是这道题目的难点所在,但如果想通了k==1时的情况的话,其实也不是很难然而本蒟蒻还是卡了差不多一个上午我们在求出了一开始求出了树的直径之后,我们可以把造的那条边先忽略掉,然而,要怎么解决有些边已经被省下的情况呢?

首先,我们得发现,当我们加入了一条边后,就多了一个环这棵树就变成了一颗基环树,所以当我们加入第二条边时,他会出现第二个环,这个环的情况也不多,不如再来一波分类讨论

  1. 此环的边不和第一个环有重叠
  2. 此环的边和第一个环没有重叠

当第二种情况时,不用想就应该知道,只要再贪心一次(把第一条直径上的边忽略)就可以了,所以我们真正的挑战在第一种情况

敲黑板,以下是重点

当我们的第二个环有与第一个环有重边时,因为造出来的路必须只经过一次,所以这个环肯定是要遍历一次的,也就是相当于我们第一次造路并没有帮重叠的边省下路程,那么怎么处理这种情况呢??

想必各位dalao都知道,

x-y-(-y)=x

bingo!!!我们只要把第一条直径上的边的边权改为 -1 (或一个你喜欢的负数) 就行啦

敲黑板,重点结束

那么我们怎么样才能把直径上的边权改为 -1 呢?

void del(int now)
{
    while(pre[now]){
        int fa=pre[now];
        for(int i=head[fa];i;i=e[i].next){
            if(e[i].to==now){
                e[i].v=-1;
                break;
            }
        }
        for(int i=head[now];i;i=e[i].next){
            if(e[i].to==fa){
                e[i].v=-1;
                break;
            }
        }
        now=fa;
    }
}

 代码如下:

#include
#include
#include
#include
#include
using namespace std;
const int N=100001;
const int M=200001; 
int n,k,cnt,t=1,maxn,zj,ans;
bool vis[N];
queue q;
struct node
{
    int next,to,v;
}e[M];
int head[N],dis[N],pre[N];
inline void add(int x,int y)
{
    e[++cnt].next=head[x];
    e[cnt].to=y;
    e[cnt].v=1;
    head[x]=cnt;
}
void bfs(int u,int pd)
{
    q.push(u);
    while(q.size())
    {
        int now=q.front();q.pop();

        for(int i=head[now];i;i=e[i].next)
        {
            int to=e[i].to;
            if(to==u||dis[to]) continue;
            dis[to]=dis[now]+1;
            if(pd)pre[to]=now;
            q.push(to);
        }
    }
}
void del(int now)
{
    while(pre[now]){
        int fa=pre[now];
        for(int i=head[fa];i;i=e[i].next){
            if(e[i].to==now){
                e[i].v=-1;
                break;
            }
        }
        for(int i=head[now];i;i=e[i].next){
            if(e[i].to==fa){
                e[i].v=-1;
                break;
            }
        }
        now=fa;
    }
}
void dp(int x)
{
    vis[x]=1;
    for(int i=head[x];i;i=e[i].next){
        int y=e[i].to;
        if(vis[y]) continue;
        dp(y);
        maxn=max(maxn,dis[x]+dis[y]+e[i].v);
        dis[x]=max(dis[x],dis[y]+e[i].v);
    }
}
void get()
{
    maxn=0;
    for(int i=1;i<=n;i++)
        if(dis[i]>maxn)
            maxn=dis[i],t=i;
}
int main()
{
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n-1;i++){
        int a,b;
        scanf("%d%d",&a,&b);
        if(a==b) continue;
        add(a,b);add(b,a);
    }
    bfs(1,0);
    get();
    memset(dis,0,sizeof(dis));
    maxn=0;
    bfs(t,1);
    get();
    ans=2*n-maxn;
    if(k==1){
        printf("%d",ans-1);
        return 0;
    }
    del(t);
    memset(dis,0,sizeof(dis));
    memset(vis,0,sizeof(vis));
    maxn=0;
    dp(1);
    printf("%d",ans-maxn);
    return 0;
} 

 

你可能感兴趣的:(图论-树的直径)