zoukankan      html  css  js  c++  java
  • 树的直径初探+Luogu P3629 [APIO2010]巡逻【树的直径】By cellur925

    题目传送门

    我们先来介绍一个概念:树的直径。

    树的直径:树中最远的两个节点间的距离。(树的最长链)
    树的直径有两种方法,都是$O(N)$。

    第一种:两遍bfs/dfs(这里写的是两遍bfs)

    从任意一个节点出发,遍历一遍树找到与出发点距离最远的点p。

    再从节点p出发,遍历一遍求出与p距离最远的点q。则pq即为直径(其中一个)

    但是不能处理负权边。

    int bfs(int x)
    {
        queue<int>q;
        memset(d,0x3f,sizeof(d));
        memset(pre,0,sizeof(pre));
        fake=d[233];
        q.push(x);d[x]=0;
        while(!q.empty())
        {
            int u=q.front();q.pop();
            for(int i=head[u];i;i=edge[i].next)
            {
                int v=edge[i].to;
                if(d[v]==fake) q.push(v),pre[v]=i,d[v]=d[u]+1;
            }
        }
        int top=x;
        for(int i=1;i<=n;i++)
            if(d[i]>d[top]) top=i;
        return top; 
    }
    int get_d()
    {
        p=bfs(1);
        p=bfs(p);
        return d[p];
    }

    第二种:树形dp

    void Treedp(int u)
    {
        vis[u]=1;
        for(int i=head[u];i;i=edge[i].next)
        {
            int v=edge[i].to;
            if(vis[v]) continue;
            Treedp(v);
            ans=max(ans,f[x]+f[y]+edge[i].val)
            f[x]=max(f[x],f[y]+edge[i].val);
        }
    }

    题目大意:给你一棵树,你需要把这棵树上的每条边至少遍历一次,走过一条边的代价是1,现在你可以添加1或2条边,新添的边可且仅可遍历一次,问最小代价是多少。

    不加边的时候,答案就是$2*(n-1)$。

    加一条新道路后,因为新道路必须经过恰好一次,设$l$,$r$为新建道路的两端,那么从$l$去$r$的时候走新道路,回来的时候走原来的道路。形成了一个环,也就是说其他与$l$,$r$无关的点还是走过两次,而连接$l$,$r$的路径上的边走一次就行了。而我们贪心的选择树中最长的路径,那就是树的直径。设直径为$d$,那么答案就是$2*(n-1)-d+1$。(+1是新建的那条道路)

    加两条新道路后,也会形成一个环,但是我们不知道这个环与之前的那个环是不是有重叠关系。若重叠,那么两个环重叠的部分就不会被经过;不重叠还好说。那么我们如何解决?处理重叠情况,我们的目标是使重叠部分恰好经过两次,那么我们可以把第一次的直径上的所有边取反(1变成-1),再在取反了的树上找直径。最终答案就是

    $2*(n-1)-(l1-1)-(l2-1)$。

    Code

     1 #include<cstdio>
     2 #include<algorithm>
     3 #include<cstring>
     4 #include<queue>
     5 #define maxn 100090
     6 
     7 using namespace std;
     8 
     9 int n,k,tot=1,fake,p,ans1,ans2;
    10 int head[maxn],d[maxn],pre[maxn],vis[maxn],f[maxn];
    11 struct node{
    12     int to,next,val;
    13 }edge[maxn*2];
    14 
    15 void add(int x,int y)
    16 {
    17     edge[++tot].to=y;
    18     edge[tot].next=head[x];
    19     head[x]=tot;
    20     edge[tot].val=1;
    21 }
    22 
    23 int bfs(int s)
    24 {
    25     queue<int>q;
    26     memset(pre,0,sizeof(pre));
    27     memset(d,0x3f,sizeof(d));
    28     fake=d[233];
    29     q.push(s);d[s]=0;
    30     while(!q.empty())
    31     {
    32         int u=q.front();q.pop();
    33         for(int i=head[u];i;i=edge[i].next)
    34         {
    35             int v=edge[i].to;
    36             if(d[v]==fake) d[v]=d[u]+edge[i].val,pre[v]=i,q.push(v);
    37         }
    38     }
    39     int top=s;
    40     for(int i=1;i<=n;i++) if(d[i]>d[top]) top=i;
    41     return top;
    42 }
    43 
    44 void get_d()
    45 {
    46     p=bfs(1);
    47     p=bfs(p); 
    48 }
    49 
    50 void Treedp(int u)
    51 {
    52     vis[u]=1;
    53     for(int i=head[u];i;i=edge[i].next)
    54     {
    55         int v=edge[i].to;
    56         if(vis[v]) continue;
    57         Treedp(v);
    58         ans2=max(ans2,f[u]+f[v]+edge[i].val);
    59         f[u]=max(f[u],f[v]+edge[i].val);
    60     }
    61 }
    62 
    63 int main()
    64 {
    65     scanf("%d%d",&n,&k);
    66     for(int i=1;i<=n-1;i++)
    67     {
    68         int x=0,y=0;
    69         scanf("%d%d",&x,&y);
    70         add(x,y),add(y,x);
    71     }
    72     get_d();
    73     ans1=d[p];
    74     if(k==1){printf("%d",2*(n-1)-d[p]+1);return 0;}
    75     for(int i=pre[p];i;i=pre[edge[i^1].to])
    76         edge[i].val=-1,edge[i^1].val=-1;
    77     Treedp(1);
    78     printf("%d",2*(n-1)-(ans1-1)-(ans2-1));
    79     return 0;
    80 }
    View Code
  • 相关阅读:
    词云
    结巴分词
    重复值处理
    异常值判断
    MySQL基本使用
    缺失值处理
    fit_transform和transform的区别
    sklearn学习笔记之简单线性回归
    图解机器学习
    Unity---UNet学习(1)----基本方法介绍
  • 原文地址:https://www.cnblogs.com/nopartyfoucaodong/p/9795820.html
Copyright © 2011-2022 走看看