好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

编程之美 2013 全国挑战赛 资格赛

编程之美 2013 全国挑战赛 资格赛

编程之美 2013 全国挑战赛 资格赛 题目三 树上的三角形

题目三 树上的三角形

时间限制: 2000ms 内存限制: 256MB

描述

有一棵树,树上有只毛毛虫。它在这棵树上生活了很久,对它的构造了如指掌。所以它在树上从来都是走最短路,不会绕路。它还还特别喜欢三角形,所以当它在树上爬来爬去的时候总会在想,如果把刚才爬过的那几根树枝/树干锯下来,能不能从中选三根出来拼成一个三角形呢?

输入

输入数据的第一行包含一个整数 T,表示数据组数。

接下来有 T 组数据,每组数据中:

第一行包含一个整数 N,表示树上节点的个数(从 1 到 N 标号)。

接下来的 N-1 行包含三个整数 a, b, len,表示有一根长度为 len 的树枝/树干在节点 a 和节点 b 之间。

接下来一行包含一个整数 M,表示询问数。

接下来M行每行两个整数 S, T,表示毛毛虫从 S 爬行到了 T,询问这段路程中的树枝/树干是否能拼成三角形。

输出

对于每组数据,先输出一行"Case #X:",其中X为数据组数编号,从 1 开始。

接下来对于每个询问输出一行,包含"Yes"或“No”,表示是否可以拼成三角形。

数据范围

样例输入

2
5
1 2 5
1 3 20
2 4 30
4 5 15
2
3 4
3 5
5
1 4 32
2 3 100
3 5 45
4 5 60
2
1 4
1 3

样例输出

Case #1:
No
Yes
Case #2:
No
Yes

解题思路

这道题如果直接按照题意去写,那么可以利用广度优先搜索得到最短路径(因为这是一颗树,而不是图,所以不必使用最短路算法),然后判断路径上的边是否能组成一个三角形(先对路径排序,然后用两边之和大于第三边进行判断)。不过搜索的时间复杂度是  O ( N ) ,判断三角形的时间复杂度为  O ( l lg l ) (其中  l  是最短路径的长度),小数据没问题,但大数据肯定会挂。

判断三角形是否存在,我并没有更好的办法,那么只能在求最短路径上下手了,以下面的树作为例子(题目没说是几叉树,不过没有关系):

图 1 一颗树的示例

求一棵树上两个节点的最短路径,其实就是求两个节点的 最近公共祖先(Least Common Ancestors,LCA) 。最近公共祖先指的是在一颗有根树中,找到两个节点 u 和 v 最近的公共祖先。这个概念很容易理解,例如上面节点 5 和 10 的 LCA 就是 1,3 和 11 的 LCA 是 3,7 和 9 的 LCA 是 3。

显然,两个节点与它们的最近公共祖先之间的路径(可以不断向上查找父节点得到)加起来,就是两个节点间的最短路径。上面节点 5 和 10 的最短路径就为 5、2、 1 、3、8、10;节点 3 和 11 的最短路径就为  3 、8、9、11。

求 LCA 有两种算法,一种是离线的 Tarjan 算法,计算出所有  M  个询问所需的时间复杂度是  O ( N + M ) ;另一种是基于 区间最值查询(Range Minimum/Maximum Query,RMQ) 的在线算法,预处理时间是  O ( N lg N ) ,每次询问的时间复杂度为  O ( 1 ) ,总得时间复杂度就是  O ( N lg N + M ) 。两个算法使用那个都可以,不过感觉还是用 Tarjan 更好点,占用内存更少,速度也更快。关于这两个算法的详细解释,可以参见 算法之LCA与RMQ问题 ,这里就不详细说明了。

在线算法的代码

View Code

  1  #include <stdio.h>
   2  #include <cmath>
   3  #include <algorithm>
   4  #include <list>
   5  #include < string .h>
   6   using   namespace   std;
    7   //   树的节点 
   8   struct   Node {
    9       int   next, len;
   10      Node ( int  n,  int   l):next(n), len(l) {}
   11   };
   12   int  pow2[ 20  ];
   13  list<Node> nodes[ 100010  ];
   14   bool  visit[ 100010  ];
   15   int  ns[ 200010  ];
   16   int   nIdx;
   17   int  length[ 100010  ];
   18   int  parent[ 100010  ];
   19   int  depth[ 200010  ];
   20   int  first[ 100010  ];
   21   int  mmin[ 20 ][ 200010  ];
   22   int  edges[ 100010  ];
   23   //   DFS 对树进行预处理 
  24   void  dfs( int  u,  int   dep)
   25   {
   26      ns[++nIdx] = u; depth[nIdx] =  dep;
   27      visit[u] =  true  ;
   28       if  (first[u] == - 1 ) first[u] =  nIdx;
   29      list<Node>::iterator it = nodes[u].begin(), end =  nodes[u].end();
   30       for  (;it != end; it++ )
   31       {
   32           int  v = it-> next;
   33           if (! visit[v])
   34           {
   35              length[v] = it-> len;
   36              parent[v] =  u;
   37              dfs(v, dep +  1  );
   38              ns[++nIdx] =  u;
   39              depth[nIdx] =  dep;
   40           }
   41       }
   42   }
   43   //   初始化 RMQ 
  44   void   init_rmq()
   45   {
   46      nIdx =  0  ;
   47      memset(visit,  0 ,  sizeof  (visit));
   48      memset(first, - 1 ,  sizeof  (first));
   49      depth[ 0 ] =  0  ;
   50      length[ 1 ] = parent[ 1 ] =  0  ;
   51      dfs( 1 ,  1  );
   52      memset(mmin,  0 ,  sizeof  (mmin));
   53       for ( int  i =  1 ; i <= nIdx; i++ ) {
   54          mmin[ 0 ][i] =  i;
   55       }
   56       int  t1 = ( int )(log(( double )nIdx) / log( 2.0  ));
   57       for ( int  i =  1 ; i <= t1; i++ ) {
   58           for ( int  j =  1 ; j + pow2[i] -  1  <= nIdx; j++ ) {
   59               int  a = mmin[i- 1 ][j], b = mmin[i- 1 ][j+pow2[i- 1  ]];
   60               if (depth[a] <=  depth[b]) {
   61                  mmin[i][j] =  a;
   62              }  else   {
   63                  mmin[i][j] =  b;
   64               }
   65           }
   66       }
   67   }
   68   //   RMQ 询问 
  69   int  rmq( int  u,  int   v)
   70   {
   71       int  i = first[u], j =  first[v];
   72       if (i >  j) swap(i, j);
   73       int  t1 = ( int )(log(( double )j - i +  1 ) / log( 2.0  ));
   74       int  a = mmin[t1][i], b = mmin[t1][j - pow2[t1] +  1  ];
   75       if (depth[a] <=  depth[b]) {
   76           return   ns[a];
   77      }  else   {
   78           return   ns[b];
   79       }
   80   }
   81  
  82   int   main() {
   83       for ( int  i =  0 ; i <  20 ; i++ ) {
   84           pow2[i] =  1  <<  i;
   85       }
   86       int   T, n, m, a, b, len;
   87      scanf( "  %d   " , & T);
   88       for  ( int  caseIdx =  1 ;caseIdx <= T;caseIdx++ ) {
   89          scanf( "  %d  " , & n);
   90           for  ( int  i =  0 ;i <= n;i++ ) {
   91               nodes[i].clear();
   92           }
   93           for  ( int  i =  1 ;i < n;i++ ) {
   94              scanf( "  %d%d%d  " , &a, &b, & len);
   95               nodes[a].push_back(Node(b, len));
   96               nodes[b].push_back(Node(a, len));
   97           }
   98           init_rmq();
   99          scanf( "  %d  " , & m);
  100          printf( "  Case #%d:\n  "  , caseIdx);
  101           for  ( int  i =  0 ;i < m;i++ ) {
  102              scanf( "  %d%d  " , &a, & b);
  103               //   利用 RMQ 得到 LCA 
 104               int  root =  rmq(a, b);
  105               bool  success =  false  ;
  106               int  l =  0  ;
  107               while  (a !=  root) {
  108                  edges[l++] =  length[a];
  109                  a =  parent[a];
  110               }
  111               while  (b !=  root) {
  112                  edges[l++] =  length[b];
  113                  b =  parent[b];
  114               }
  115               if  (l >=  3  ) {
  116                  sort(edges, edges +  l);
  117                   for  ( int  j =  2 ;j < l;j++ ) {
  118                       if  (edges[j -  2 ] + edges[j -  1 ] >  edges[j]) {
  119                          success =  true  ;
  120                           break  ;
  121                       }
  122                   }
  123               }
  124               if   (success) {
  125                  puts( "  Yes  "  );
  126              }  else   {
  127                  puts( "  No  "  );
  128               }
  129           }
  130       }
  131       return   0  ;
  132  }

离线算法的代码

View Code

  1  #include <stdio.h>
   2  #include < string .h>
   3  #include <list>
   4  #include <algorithm>
   5   using   namespace   std;
    6   //   树和查询的节点 
   7   struct   Node {
    8       int   next, len;
    9      Node ( int  n,  int   l):next(n), len(l) {}
   10   };
   11  list<Node> nodes[ 100010  ];
   12  list<Node> querys[ 100010  ];
   13   bool  visit[ 100010  ];
   14   int  ancestor[ 100010  ];
   15   int  parent[ 100010  ];
   16   int  length[ 100010  ];
   17   int  edges[ 100010  ];
   18   //   查询的结果 
  19   bool  result[ 100010  ];
   20   //   并查集 
  21   int  uset[ 100010  ];
   22   int  find( int   x) {
   23       int  p =  x, t;
   24       while  (uset[p] >=  0 ) p =  uset[p];
   25       while  (x != p) { t = uset[x]; uset[x] = p; x =  t; }
   26       return   x;
   27   }
   28   void  un_ion( int  a,  int   b) {
   29       if  ((a = find(a)) == (b = find(b)))  return  ;
   30       if  (uset[a] < uset[b]) { uset[a] += uset[b]; uset[b] =  a; }
   31       else  { uset[b] += uset[a]; uset[a] =  b; }
   32   }
   33   void   init_uset() {
   34      memset(uset, - 1 ,  sizeof  (uset));
   35   }
   36  
  37   void  tarjan( int   u) {
   38      visit[u] =  true  ;
   39      ancestor[find(u)] =  u;
   40      list<Node>::iterator it = nodes[u].begin(), end =  nodes[u].end();
   41       for  (;it != end; it++ )
   42       {
   43           int  v = it-> next;
   44           if (! visit[v])
   45           {
   46              length[v] = it-> len;
   47              parent[v] =  u;
   48               tarjan(v);
   49               un_ion(u, v);
   50              ancestor[find(u)] =  u;
   51           }
   52       }
   53      it = querys[u].begin(); end =  querys[u].end();
   54       for  (;it != end; it++ )
   55       {
   56           int  v = it-> next;
   57           if  (visit[v])
   58           {
   59               //   处理从 u 起始的查询 
  60               int  root =  ancestor[find(v)];
   61               int  l =  0  ;
   62               int  a =  u;
   63               while  (a !=  root) {
   64                  edges[l++] =  length[a];
   65                  a =  parent[a];
   66               }
   67               while  (v !=  root) {
   68                  edges[l++] =  length[v];
   69                  v =  parent[v];
   70               }
   71              sort(edges, edges +  l);
   72               for  ( int  j =  2 ;j < l;j++ ) {
   73                   if  (edges[j -  2 ] + edges[j -  1 ] >  edges[j]) {
   74                      result[it->len] =  true  ;
   75                       break  ;
   76                   }
   77               }
   78           }
   79       }
   80   }
   81  
  82   int   main() {
   83       int   T, n, m, a, b, len;
   84      scanf( "  %d   " , & T);
   85       for  ( int  caseIdx =  1 ;caseIdx <= T;caseIdx++ ) {
   86          scanf( "  %d  " , & n);
   87           for  ( int  i =  0 ;i <= n;i++ ) {
   88               nodes[i].clear();
   89               querys[i].clear();
   90           }
   91           for  ( int  i =  1 ;i < n;i++ ) {
   92              scanf( "  %d%d%d  " , &a, &b, & len);
   93               nodes[a].push_back(Node(b, len));
   94               nodes[b].push_back(Node(a, len));
   95           }
   96          scanf( "  %d  " , & m);
   97           for  ( int  i =  0 ;i < m;i++ ) {
   98              scanf( "  %d%d  " , &a, & b);
   99               //   查询要添加两遍,以防止出现遗漏 
 100               querys[a].push_back(Node(b, i));
  101               querys[b].push_back(Node(a, i));
  102           }
  103          printf( "  Case #%d:\n  "  , caseIdx);
  104           init_uset();
  105          memset(visit,  0 ,  sizeof  (visit));
  106          memset(result,  0 ,  sizeof  (result));
  107          length[ 1 ] = parent[ 1 ] =  0  ;
  108          tarjan( 1  );
  109           for  ( int  i =  0 ;i < m;i++ ) {
  110               if   (result[i]) {
  111                  puts( "  Yes  "  );
  112              }  else   {
  113                  puts( "  No  "  );
  114               }
  115           }
  116       }
  117       return   0  ;
  118  }

这两个算法应该是没问题的,但大数据的时候都 TLE 了,看来 list 真不能随便用,动态开辟内存还是太慢了。离线算法的内存使用大概只有在线算法的 70%。

后来我翻代码的时候(所有人的代码都可以看到,这点挺给力),看到有人没用上面的 LCA 算法,而是在用 DFS 建好树后,使要判断的两个节点  u  和  v  分别沿着父节点链向上遍历,同时保持  u  和  v  的深度是相同的,这样同样能得到最短路径和 LCA,只不过时间复杂度要高一些。但在这道题中也没有关系,因为在找三角形时还是需要把路径遍历一编才可以,LCA 的计算反而会带来额外的复杂性,看来的确是自己想复杂了。

这段遍历算法大概类似于下面这样:

  1   while  (deep(u) >  deep(v)){
   2       //

算法:Eratosthenes 筛选求质数

说明:

   除了自身之外,无法被其它整数整除的数称之为质数,要求质数很简单,但如何快速的 求出质数则一直是程式设计人员与数学家努力的课题, 在这边介绍一个着名的Eratosthenes求质 数方法。

 

解法:

  首先知道这个问题可以使用回圈来求解,将一个指定的数除以所有小于它的数,若可以 整除就不是质数,然而如何减少回圈的检查次数?如何求出小于N的所有质数?

 

 首先假设要检查的数是N好了,则事实上只要检查至N的开根号就可以了,道理很简单,假设 A*B = N,如果A大于N的开根号,则事实上在小于A之前的检查就可以先检查到B这个数可以整 除N。不过在程式中使用开根号会精确度的问题,所以可以使用i*i <= N进行检查,且执行更快。

 

再来假设有一个筛子存放1~N,例如:

 

2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 ........ N

 

先将2的倍数筛去: 2 3 5 7 9 11 13 15 17 19 21 ........ N

 

再将3的倍数筛去: 2 3 5 7 11 13 17 19 ........ N

 

再来将5的倍数筛去,再来将7的质数筛去,再来将11的倍数筛去........,如此进行到最后留下的 数就都是质数,这就是Eratosthenes筛选方法(Eratosthenes Sieve Method)。

 

*/

 public   class   Eratosthenes {

      /**  
     *   @param   args
       */ 
     public   static   void   main(String[] args) {
          int  N = 100 ;
          int  i = 0, j = 0 , count = 0 ;
          int  prime[] =  new   int [N + 1 ];

          //  初始化数据 
         for  (i = 2; i <= N; i++ ) {
            prime[i]  = 1 ;
        }
          //  循环1(N 开方 次) 
         for  (i = 2; i * i <= N; i++ ) {
              if  (prime[i] == 0 ) {
                count ++ ;
                  continue  ;
            }
              //  循环2(N/i 次)  筛选被i整除的数  
             for  (j = i * i; j <= N; j = j +  i) {
                prime[j]  = 0 ;
                count ++ ;
            }
        }

        System.out.println( "Times of calculation : " +  count);
        j =0 ;
          for  (i = 2; i <= N; i++ ) {
              if  (prime[i] == 1 ) {
                System.out.print( "\t" );
                System.out.print(i);
                j ++ ;
                  if (j % 10 == 0 ){
                    System.out.println();
                }
            }

        }

    }

} 

循环次数 O(N):

N 进入循环的次数 循环次数/N 100 109 1.09 1000 1430 1.43 10000 17055 1.70 100000 193328 1.93 1000000 2122879 2.12 10000000 22852766 2.28

质数可以去 http://HdhCmsTestrapidtables测试数据/math/algebra/Ln.htm 进行校验。

 

 

标签:  Eratosthenes ,  质数 ,  prime

   

作者: Leo_wl

    

出处: http://HdhCmsTestcnblogs测试数据/Leo_wl/

    

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

版权信息

查看更多关于编程之美 2013 全国挑战赛 资格赛的详细内容...

  阅读:37次