zoukankan      html  css  js  c++  java
  • 【树形dp 最长链】bzoj1912: [Apio2010]patrol 巡逻

    富有思维性的树形dp

    Description

    Input

    第一行包含两个整数 n, K(1 ≤ K ≤ 2)。接下来 n – 1行,每行两个整数 a, b, 表示村庄a与b之间有一条道路(1 ≤ a, b ≤ n)。

    Output

    输出一个整数,表示新建了K 条道路后能达到的最小巡逻距离。

    Sample Input

    8 1
    1 2
    3 1
    3 4
    5 3
    7 5
    8 5
    5 6

    Sample Output

    11

    HINT

    10%的数据中,n ≤ 1000, K = 1; 
    30%的数据中,K = 1; 
    80%的数据中,每个村庄相邻的村庄数不超过 25; 
    90%的数据中,每个村庄相邻的村庄数不超过 150; 
    100%的数据中,3 ≤ n ≤ 100,000, 1 ≤ K ≤ 2。


    题目分析

    初看这题觉得毫无头绪,好像怎么也不能把它和最长链联系在一起。特别是新建的边必须经过一次的限制,让人一脸懵逼。

    k=0

    不过首先挖掘性质:显然的是,若只是树形图,路径最短为$2n-2$;并且实际上起点任意对于答案来说都是一样的。

    k=1

    然后我们来想一想$k=1$的情况。比如现在我们有一颗树长成这样:

    然后我们现在添加一条边:

    可以发现形成的环上,若环长度为$lens$,那么需要经过的路径就从$2*lens$变为了$lens+1$。并且对于其他节点来说,它们的花费是不改变的。

    由此自然想到我们将最长链的首尾相连,就可以得到$k=1$时的答案。

    k=2

    有了k=1,扩展至k=2的思路大致相同。除了最长链形成的环,我们需要在树上另找一条次长链。

    这里有一个技巧就是把最长链上的边权全都改为-1.引用CQzhangyu的一段话:

    一开始想的是将直径拎出来,然后跑一个非常复杂的树形DP,但是看了题解。。。直接将直径上的所有边权值设为-1,再求一遍直径即可。正确性如何保证?如果这两条路径不相交,显然正确;如果相交,那么相当于将原路径拆成了两条。所以做法还是很巧妙的~

    还有Coco_T的另一段话

    如果我们什么处理都没有,直接求一个次长链(次短路方法), 
    可能会和最长链重合,那么最长链上的一部分就会走两遍 
    所以我们在求出最长链之后,把最长链上的边权赋为-1, 
    这样再跑一个裸的直径就好了 
    (这样就可以保证可以在新求出的直径中尽量少重合原先的直径)

    其实感觉能够感性理解,但是好像依旧不甚明白……?

    还有要注意的是:

    1     if (k==2){
    2         mx = 0;
    3         for (int i=s1[dir]; i!=-1; i=s1[edges[i].y])
    4             edges[i].val = edges[i^1].val = -1;
    5         for (int i=s2[dir]; i!=-1; i=s1[edges[i].y])
    6             edges[i].val = edges[i^1].val = -1;
    7         dfs(1, 0);
    8         ans = ans-mx+1;
    9     }

    这里第二部分的作用是,将dir的次长链的边权赋为-1.乍一眼看上去好像应该是for (int i=s2[dir]; i!=-1; i=s1[edges[i].y]),不过实际上次长链除了头上是s2,后面的路径走的都是其最大值

     1 #include<bits/stdc++.h>
     2 const int maxn = 100035;
     3 
     4 struct Edge
     5 {
     6     int y,val;
     7     Edge(int a=0, int b=0):y(a),val(b) {}
     8 }edges[maxn<<1];
     9 int n,k,mx,dir,ans;
    10 int edgeTot,nxt[maxn<<1],head[maxn];
    11 int s1[maxn],s2[maxn];
    12 
    13 int read()
    14 {
    15     char ch = getchar();
    16     int num = 0;
    17     bool fl = 0;
    18     for (; !isdigit(ch); ch = getchar())
    19         if (ch=='-') fl = 1;
    20     for (; isdigit(ch); ch = getchar())
    21         num = (num<<1)+(num<<3)+ch-48;
    22     if (fl) num = -num;
    23     return num;
    24 }
    25 void addedge(int u, int v)
    26 {
    27     edges[++edgeTot] = Edge(v, 1), nxt[edgeTot] = head[u], head[u] = edgeTot;
    28     edges[++edgeTot] = Edge(u, 1), nxt[edgeTot] = head[v], head[v] = edgeTot;
    29 }
    30 int dfs(int now, int fa)
    31 {
    32     int mx1 = 0, mx2 = 0;
    33     for (int i=head[now]; i!=-1; i=nxt[i])
    34         if (edges[i].y!=fa){
    35             int tt = dfs(edges[i].y, now)+edges[i].val;
    36             if (tt > mx1)
    37                 mx2 = mx1, mx1 = tt, s2[now] = s1[now], s1[now] = i;
    38             else if (tt > mx2) mx2 = tt, s2[now] = i;
    39         }
    40     if (mx1+mx2 > mx) mx = mx1+mx2, dir = now;
    41     return mx1;
    42 }
    43 int main()
    44 {
    45     memset(head, -1, sizeof head);
    46     memset(s1, -1, sizeof s1);
    47     memset(s2, -1, sizeof s2);
    48     n = read(), k = read();
    49     for (int i=1; i<n; i++)
    50         addedge(read(), read());
    51     dfs(1, 0);
    52     ans = 2*n-mx-1;
    53     if (k==2){
    54         mx = 0;
    55         for (int i=s1[dir]; i!=-1; i=s1[edges[i].y])
    56             edges[i].val = edges[i^1].val = -1;
    57         for (int i=s2[dir]; i!=-1; i=s1[edges[i].y])
    58             edges[i].val = edges[i^1].val = -1;
    59         dfs(1, 0);
    60         ans = ans-mx+1;
    61     }
    62     printf("%d
    ",ans);
    63     return 0;
    64 }

    END

  • 相关阅读:
    Kubernetes---启动及退出动作
    Kubernetes---容器探针
    Kubernetes---容器的生命周期
    红米3 SudaMod(android_6.01_r72)高配指纹/农历/归属地/SM天气/流畅运行/红外线正常/更新于20161025
    解决:WPS for Linux提示“系统缺失字体symbol、wingdings、wingdings 2、wingdings 3、webding”
    教你一招:Excel中使用MID函数获取身份证中的出生年月日
    解决: Sudamod/CM-13.0 源代码出现 Fatal: duplicate project .....问题
    使用jquery为个人博客园首页公告栏添加用户登录与注销
    使用jquery脚本获取随笔、文章和评论的统计数,自定义显示位置
    使用iquery为博客园(或网站)添加动态显示时间(格式为:年 月 日 时间 星期几)
  • 原文地址:https://www.cnblogs.com/antiquality/p/9248589.html
Copyright © 2011-2022 走看看