zoukankan      html  css  js  c++  java
  • 【最小生成树杂题】

    • 这里谈一下最小生成树
    • 生成树的概念:连通图G的一个子图如果是一棵包含G的所有顶点的树,则该子图称为G的生成树。生成树是连通图的极小连通子图。所谓极小是指:若在树中任意增加一条边,则将出现一个回路;若去掉一条边,将会使之变成非连通图。 生成树各边的权值总和称为生成树的权。权最小的生成树称为最小生成树。
    • 最小生成树一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边。常用于求最小生成树得算法包括kruskal(克鲁斯卡尔)算法或Prim(普里姆)算法。
    • 最常用的是kruscal算法。kruscal算法的实质是用贪心策略来求得图的最小生成树。初始状态图中一条边也没有,每个点独立的存在于集合中;每次取出当前边权最小的边,判断能否将此边加入生成树中,判断依据:边的两个端点是否在一个集合中。若两点在一个集合中,则不需加入该边;若两点不在一个集合中,则将两点所在的集合合并,即集合中的点可以互相到达,无需两两之间再建边;如此做直到集合中各个点都联通或边全部枚举完毕;若此图联通,则为最小生成树。保证边权递增可以用快速排序的思想,维护集合关系可以用并查集的思想。排序复杂度O(eloge),枚举复杂度O(e),总体复杂度O(eloge)。
    • 相比于kruscal算法,prim算法比较复杂;同样的是,prim算法也使用了贪心的策略,不过该算法是由点来贪心的。初始时边集u为空,点集v有一个点x,x为任意一点。操作步骤:在所有点中找到一点y,使d[y,r]最小,其中r∈v;把该边加入u,改点加入v,更新答案。如此做直到每个点都在点集中或某些点无法加入点集。若所有点都在点集中,则为最小生成树。枚举每个点加入集合,复杂度O(n),枚举该点与点集的距离,复杂度O(n),总体复杂度O(n2)。若用堆优化枚举距离的过程,复杂度降至O(nlogn)。
    • 下面是关于最小生成树的几道杂题。

    Problem 1 最小生成树

    题目描述

    如题,给出一个无向图,求出最小生成树,如果该图不连通,则输出orz

    输入输出格式

    输入格式:

    第一行包含两个整数N、M,表示该图共有N个结点和M条无向边。(N<=5000,M<=200000)

    接下来M行每行包含三个整数Xi、Yi、Zi,表示有一条长度为Zi的无向边连接结点Xi、Yi

    输出格式:

    输出包含一个数,即最小生成树的各边的长度之和;如果该图不连通则输出orz

    输入输出样例

    输入样例#1:
    4 5
    1 2 2
    1 3 2
    1 4 3
    2 3 4
    3 4 3
    输出样例#1:
    7

    说明

    时空限制:1000ms,128M

    数据规模:

    对于20%的数据:N<=5,M<=20

    对于40%的数据:N<=50,M<=2500

    对于70%的数据:N<=500,M<=10000

    对于100%的数据:N<=5000,M<=200000

    样例解释:

    所以最小生成树的总边权为2+2+3=7

    • 最小生成树模板题,由数据范围可知,kruscal算法即可。复杂度O(mlogm)。
     1 #include <cstdio>
     2 #include <iostream>
     3 #include <algorithm>
     4 using namespace std;
     5 
     6 struct bian {
     7     int x,y,a;    
     8 } e[200050];
     9 
    10 bool cmp(const bian p,const bian q){
    11     return p.a<q.a;
    12 }
    13 
    14 int n,m,ans,t,father[5050];
    15 
    16 int gf(int x) {
    17     if (father[x]==x) return(x);
    18     father[x]=gf(father[x]);
    19     return(father[x]);
    20 }
    21 
    22 int main(){
    23     scanf("%d%d",&n,&m);
    24     for (int i=1; i<=m; i++) scanf("%d%d%d",&e[i].x,&e[i].y,&e[i].a);
    25     for (int i=1; i<=n; i++) father[i]=i;
    26     sort(e+1,e+m+1,cmp);
    27     for (int i=1; i<=m; i++) {
    28         int fx=gf(e[i].x);
    29         int fy=gf(e[i].y);
    30         if (fx!=fy) {
    31             father[fx]=fy;
    32             ans+=e[i].a;
    33         }
    34     }
    35     t=gf(1);
    36     bool f=true;
    37     for (int i=2; i<=n; i++) {
    38         if (gf(i)!=t) {
    39             printf("orz");
    40             f=0;
    41             break;
    42         }
    43     }
    44     if (f) printf("%d",ans);
    45     return 0;
    46 }

    Problem 2 公路修建

    题目描述

    某国有n个城市,它们互相之间没有公路相通,因此交通十分不便。为解决这一“行路难”的问题,政府决定修建公路。修建公路的任务由各城市共同完成。

    修建工程分若干轮完成。在每一轮中,每个城市选择一个与它最近的城市,申请修建通往该城市的公路。政府负责审批这些申请以决定是否同意修建。

    政府审批的规则如下:

    (1)如果两个或以上城市申请修建同一条公路,则让它们共同修建;

    (2)如果三个或以上的城市申请修建的公路成环。如下图,A申请修建公路AB,B申请修建公路BC,C申请修建公路CA。则政府将否决其中最短的一条公路的修建申请;

    (3)其他情况的申请一律同意。

    一轮修建结束后,可能会有若干城市可以通过公路直接或间接相连。这些可以互相:连通的城市即组成“城市联盟”。在下一轮修建中,每个“城市联盟”将被看作一个城市,发挥一个城市的作用。

    当所有城市被组合成一个“城市联盟”时,修建工程也就完成了。

    你的任务是根据城市的分布和前面讲到的规则,计算出将要修建的公路总长度。

    输入输出格式

    输入格式:

    第一行一个整数n,表示城市的数量。(n≤5000)

    以下n行,每行两个整数x和y,表示一个城市的坐标。(-1000000≤x,y≤1000000)

    输出格式:

    一个实数,四舍五入保留两位小数,表示公路总长。(保证有惟一解)

    输入输出样例

    输入样例#1:
    4
    0 0
    1 2
    -1 2
    0 4
    输出样例#1:
    6.47

    说明

    修建的公路如图所示:

    • 本题思路比较明确,即求出最小生成树即可。
    • 数据范围,n≤5000。相当于5000*5000条边(kruscal已gg),那只能用prim了。
    • 复杂度O(n2),应该是能通过的。
     1 #include <cstdio>
     2 #include <iostream>
     3 #include <algorithm>
     4 #include <cmath>
     5 
     6 int n,x[5050],y[5050];
     7 double d[5050],ans;
     8 bool v[5050];
     9 
    10 double cal(int a,int b)
    11 {
    12      double e1,e2;
    13      e1=abs(x[a]-x[b]);e2=abs(y[a]-y[b]);
    14      return sqrt(e1*e1+e2*e2);
    15 }      
    16 
    17 int main() {
    18     scanf("%d",&n);
    19     for (int i=1; i<=n; i++) scanf("%d%d",&x[i],&y[i]);
    20     for (int i=2; i<=n; i++) d[i]=cal(1,i);
    21     d[1]=0;
    22     v[1]=1;
    23     for (int i=2; i<=n; i++) {
    24         double minn=2147483647;
    25         int cur=0;
    26         for (int j=1; j<=n; j++) 
    27             if (!v[j] && d[j]<minn) {
    28                 minn=d[j];
    29                 cur=j;
    30             }
    31         ans=ans+d[cur];
    32         v[cur]=1;
    33         for (int j=1; j<=n; j++) if (!v[j] && d[j]>cal(cur,j)) d[j]=cal(cur,j);
    34     }
    35     printf("%.2lf",ans);
    36     return 0;
    37 }

    Problem 3 清理仓库

    题目描述

    李老师的仓库已经有很多年没有清扫了,所以这次的计划是用河水来冲。仓库是一个N*M的矩形,且每个格子里都堆满了尘土。相邻的格子之间都有门,要想让水冲进去,就必须打开这些门。这可不是一件容易的事情。因为有些格子里土堆得很高,因此打开门就很费劲。推开(或拉开)一扇从A格子到B格子的门,需要的力度值为B房间里土堆的高度。写一个程序计算至少需要花费多少力气,才能使所有的格子都进水。

    输入输出格式

    输入格式:

    第一行为N和M(N, M <= 40),代表仓库的大小。

    以后N行,每行N个整数(每个数不超过100),分别表示每个格子里土堆的厚度。

    输出格式:

    你得到的结果。所有的格子必须都进水。水是从左上角的格子进去的。

    输入输出样例

    输入样例#1:
    3 4
    3 5 2 1
    7 3 4 8
    1 6 5 7
    输出样例#1:
    26


    • 一道最小生成树建模的题。由题意,若要两个房间联通,需要打开中间的门,而开门只有两种方式,即推开和拉开,据此建边,求最小生成树即可。
    • 时间复杂度O(n*m*4*log (n*m*4)),可以通过该题。
     1 var
     2     n,m,i,j,k,fx,fy,ans        :longint;
     3     father                    :array[0..2050] of longint;
     4     a                        :array[0..2050] of longint;
     5     l,r,w                    :array[0..10050] of longint;
     6     
     7 procedure swap(var x,y:longint);
     8 var
     9     t                        :longint;
    10 begin
    11     t:=x;
    12     x:=y;
    13     y:=t;
    14 end;
    15     
    16 procedure sort(ll,rr:longint);
    17 var
    18     i,j,x                    :longint;
    19 begin
    20     i:=ll; j:=rr; x:=w[(ll+rr)>>1];
    21     while i<j do
    22     begin
    23         while w[i]<x do inc(i);
    24         while w[j]>x do dec(j);
    25         if i<=j then
    26         begin
    27             swap(w[i],w[j]);
    28             swap(l[i],l[j]);
    29             swap(r[i],r[j]);
    30             inc(i);
    31             dec(j);
    32         end;
    33     end;
    34     if i<rr then sort(i,rr);
    35     if j>ll then sort(ll,j);
    36 end;
    37 
    38 function min(x,y:longint):longint;
    39 begin
    40     if x<y then exit(x) else exit(y);
    41 end;
    42     
    43 function gf(x:longint):longint;
    44 begin
    45     if father[x]=x then exit(x);
    46     father[x]:=gf(father[x]);
    47     exit(father[x]);
    48 end;    
    49     
    50 begin
    51     read(n,m);
    52     for i:=1 to n*m do read(a[i]);
    53     for i:=1 to n*m do father[i]:=i;
    54     for i:=1 to n*m do
    55     begin
    56         if i mod m<>0 then
    57         begin
    58             inc(k);
    59             l[k]:=i;
    60             r[k]:=i+1;
    61             w[k]:=min(a[l[k]],a[r[k]]);
    62         end;
    63         if i<=(n-1)*m then
    64         begin
    65             inc(k);
    66             l[k]:=i;
    67             r[k]:=i+m;
    68             w[k]:=min(a[l[k]],a[r[k]]);
    69         end;
    70     end;
    71     sort(1,k);
    72     for i:=1 to k do
    73     begin
    74         fx:=gf(l[i]);
    75         fy:=gf(r[i]);
    76         if fx<>fy then
    77         begin
    78             father[fx]:=fy;
    79             inc(ans,w[i]);
    80         end;
    81     end;
    82     writeln(ans);
    83 end.

    Problem 4 [NOIP2013] 货车运输

    题目描述

    A 国有 n 座城市,编号从 1 到 n,城市之间有 m 条双向道路。每一条道路对车辆都有重量限制,简称限重。现在有 q 辆货车在运输货物, 司机们想知道每辆车在不超过车辆限重的情况下,最多能运多重的货物。

    输入输出格式

    输入格式:

    输入文件名为 truck.in。

    输入文件第一行有两个用一个空格隔开的整数 n,m,表示 A 国有 n 座城市和 m 条道

    路。 接下来 m 行每行 3 个整数 x、 y、 z,每两个整数之间用一个空格隔开,表示从 x 号城市到 y 号城市有一条限重为 z 的道路。意:x 不等于 y,两座城市之间可能有多条道路。

    接下来一行有一个整数 q,表示有 q 辆货车需要运货。

    接下来 q 行,每行两个整数 x、y,之间用一个空格隔开,表示一辆货车需要从 x 城市运输货物到 y 城市,注意:x 不等于 y。

    输出格式:

    输出文件名为 truck.out。

    输出共有 q 行,每行一个整数,表示对于每一辆货车,它的最大载重是多少。如果货

    车不能到达目的地,输出-1。

    输入输出样例

    输入样例#1:
    4 3
    1 2 4
    2 3 3
    3 1 1
    3
    1 3
    1 4
    1 3
    输出样例#1:
    3
    -1
    3

    说明

    对于 30%的数据,0 < n < 1,000,0 < m < 10,000,0 < q< 1,000; 对于 60%的数据,0 < n < 1,000,0 < m < 50,000,0 < q< 1,000; 对于 100%的数据,0 < n < 10,000,0 < m < 50,000,0 < q< 30,000,0 ≤ z ≤ 100,000。

     

    • 由题意可知本题要求图中联通边尽量大,所以可以想到最大生成树。
    • 首先可以用kruscal算法得到一颗最大生成树,接着要找出两点间连边最小边权的边。比较简单的想法:暴力的求出路径上边的最小值,但复杂度较高,不是理想的策略。我们可以用倍增的想法,维护两点的最近公共祖先(lca)以及它们到最近公共祖先之间的距离。该算法复杂度O(mlogm+qlogn),比较理想,可以通过本题。
      1 type
      2     rec                    =record
      3     l,r,w                :longint;
      4 end;
      5 
      6 var
      7     n,m,i,j,x,y,z,q,fx,fy,root,l    :longint;
      8     e                                :array[0..100050] of rec;
      9     pre,last,other,len,d            :array[0..100050] of longint;
     10     vis                                :Array[0..100050] of boolean;
     11     father                            :array[0..100050] of longint;
     12     anc                                :array[0..100050,0..30] of longint;
     13     minn                            :Array[0..100050,0..30] of longint;
     14     
     15 function min(x,y:longint):longint;
     16 begin
     17     if x<y then exit(x) else exit(y);
     18 end;
     19     
     20 procedure swap(var a,b:rec);
     21 var
     22     c                                :rec;
     23 begin
     24     c:=a;
     25     a:=b;
     26     b:=c;
     27 end;
     28     
     29 procedure sort(l,r:longint);
     30 var
     31     i,j,x                            :longint;
     32 begin
     33     i:=l; j:=r; x:=e[(l+r)>>1].w;
     34     while i<j do
     35     begin
     36         while e[i].w<x do inc(i);
     37         while e[j].w>x do dec(j);
     38         if i<=j then
     39         begin
     40             swap(e[i],e[j]);
     41             inc(i);
     42             dec(j);
     43         end;
     44     end;
     45     if i<r then sort(i,r);
     46     if j>l then sort(l,j);
     47 end;
     48 
     49 function gf(x:longint):longint;
     50 begin
     51     if father[x]=x then exit(x);
     52     father[x]:=gf(father[x]);
     53     exit(father[x]);
     54 end;
     55 
     56 procedure add(u,v,r:longint);
     57 begin
     58     inc(l);
     59     pre[l]:=last[u];
     60     last[u]:=l;
     61     other[l]:=v;
     62     len[l]:=r;
     63 end;
     64 
     65 procedure dfs(x:longint);
     66 var
     67     p,q                                :longint;
     68 begin
     69     p:=last[x];
     70     while p<>0 do
     71     begin
     72         q:=other[p];
     73         if (not vis[q]) then
     74         begin
     75             vis[q]:=true;
     76             d[q]:=d[x]+1;
     77             minn[q,0]:=len[p]; 
     78             anc[q,0]:=x;
     79             dfs(q);
     80         end;
     81         p:=pre[p];
     82     end;
     83 end;
     84 
     85 procedure bz;
     86 var
     87     i,j                                :longint;
     88 begin
     89     for j:=1 to 25 do
     90         for i:=1 to n do
     91         begin
     92             minn[i,j]:=min(minn[i,j-1],minn[anc[i,j-1],j-1]);
     93             anc[i,j]:=anc[anc[i,j-1],j-1];
     94         end;
     95 end;
     96 
     97 function lca(u,v:longint):longint;
     98 var
     99     t,ans,i                            :longint;
    100 begin
    101     if d[u]<d[v] then
    102     begin
    103         t:=u;
    104         u:=v;
    105         v:=t;
    106     end;
    107     ans:=maxlongint;
    108     for i:=25 downto 0 do
    109     begin
    110         if d[anc[u,i]]>=d[v] then 
    111         begin
    112             ans:=min(ans,minn[u,i]);
    113             u:=anc[u,i];
    114         end;
    115     end;
    116     if u=v then exit(ans);
    117     for i:=25 downto 0 do
    118     begin
    119         if (anc[u,i]<>anc[v,i]) then
    120         begin
    121             ans:=min(ans,minn[u,i]);
    122             ans:=min(ans,minn[v,i]);
    123             u:=anc[u,i];
    124             v:=anc[v,i];
    125         end;
    126     end;
    127     ans:=min(ans,minn[u,0]);
    128     ans:=min(ans,minn[v,0]);
    129     exit(ans);
    130 end;
    131     
    132 begin
    133     read(n,m);
    134     for i:=1 to n do father[i]:=i;
    135     for i:=1 to m do read(e[i].l,e[i].r,e[i].w);
    136     sort(1,m);
    137     for i:=m downto 1 do
    138     begin
    139         fx:=gf(e[i].l);
    140         fy:=gf(e[i].r);    
    141         if (fx<>fy) then
    142         begin
    143             father[fx]:=fy;
    144             add(e[i].l,e[i].r,e[i].w);
    145             add(e[i].r,e[i].l,e[i].w);
    146             root:=e[i].l;
    147         end;
    148     end;
    149     d[root]:=1;
    150     vis[root]:=true;
    151     dfs(root);
    152     bz;
    153     {writeln(root);
    154     for i:=1 to n do
    155     begin
    156         for j:=0 to 10 do write(anc[i,j]);
    157         writeln;
    158     end;
    159     for i:=1 to n do writeln(d[i]);}
    160     read(q);
    161     for i:=1 to q do
    162     begin
    163         read(x,y);
    164         if gf(x)<>gf(y) then writeln(-1) else writeln(lca(x,y));
    165     end;
    166 end.
  • 相关阅读:
    webpack基础
    LeetCode232. 用栈实现队列做题笔记
    mysql 时间加减一个月
    leetcode 1381. 设计一个支持增量操作的栈 思路与算法
    LeetCode 141. 环形链表 做题笔记
    leetcode 707. 设计链表 做题笔记
    leetcode 876. 链表的中间结点 做题笔记
    leetcode 143. 重排链表 做题笔记
    leetcode 1365. 有多少小于当前数字的数字 做题笔记
    LeetCode1360. 日期之间隔几天 做题笔记
  • 原文地址:https://www.cnblogs.com/zoewilly/p/6034034.html
Copyright © 2011-2022 走看看