树的直径初探+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

 

posted @ 2018-10-16 07:48  cellur925&Chemist  阅读(188)  评论(0编辑  收藏  举报