csp20160904解题报告

dijkstra+贪心

 

每次加一个到起始点(首都)距离最小的点。

然后加边,这个最小点必然通过一条边和已加入的某个点相连,在这个最小点与已加入的点相连的边中,选取最短的一条边加入。

 

证明如下:

前提:

G=<V,E,W>。点x到点y的最短距离为(途中可经过其它点)为D(x,y)或D(y,x),若y=1,则简写为D(1)。若点x和点y存在道路(直达),则其中最短的道路长度为W(x,y)或W(y,x)。

 

做法:

I.把V分成两个子集S和T。初始时,S={1},T=V-S。

II.对T中每一元素t计算D(t)(当前点t到点1的路径中,经过的点仅限为S中的点),根据D(t)值找出T中距点1最短的一结点x,写出1到x的最短路径(当前点t到点1的路径中,经过的点仅限为S中的点)的长度D(x)。在点x与S集合中的点相连的边中,选取最短的一条边加入。

III.置S为S+{x},置T为T-{x},若T为空,则停止,否则再重复2。

 

证明1:若x是T中具有最小D值的结点,则D(x)是从a到 x的最短距离。

    假设a到x中另有一条含有T中结点的最短通路,不妨设这个通路中第一个属于T-{x}的结点是t1,于是D(x)=D(t1)+D(t1,x)>D(t1)>D(x)。矛盾,所以得证。

 

D(t)的方法:

初始时,如果点t到点1存在道路,则D(t)=W(1,t),否则D(t)=无穷(一个足够大的数)。假设对T中的每一个t已计算了D值。设x是T中D值最小的一个结点,记S’=S+{x},T’=T-{x},令D’(t)表示T’中结点t的D值,则

D’(t)=min[D(t),D(x)+W(x,t)]。

 

证明2:求D(t)的方法正确

    从点1到点t的最短路径(不包含T’中的其它结点):

    情况1:它不包含T’中的其它结点,则D’(t)=D(t)。

    情况2:它从点1到点x,然后点x到点t(道路),则D’(t)=D(x)+W(x,t)。

    情况3:它从点1到点x,然后从点x到点t(非道路),则从点x到点t的路径中必经过一点,设为点y。可以理解为从点1到点t的最短路径为点1到点y,再点y到点t(不包含T’中的其它结点),而点1到点y的最短路径经过点x,有D(x)<D(y),但y是T中的点,x是T中新加入的点,y在x之前出现,有D(y)<=D(x),矛盾,所以情况3不可能出现。

    所以根据情况1、2,D’(t)=min[D(t),D(x)+W(x,t)],得证。

 

证明3:在点x与S集合中的点相连的边中,选取最短的一条边加入,所耗费的代价最小。

    如果点1到点x的最短路径为点1到点y(点x为最小点时的T集合(S集合的补集)中的点),点y到点x(道路),则D(x)=D(y)+D(x,y)>D(y)>D(x),矛盾,所以得证。

 

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <malloc.h>
  4 #include <stdbool.h>
  5 
  6 #define max_ 1000000000
  7 
  8 int main()
  9 {
 10     struct node
 11     {
 12         long point,len;
 13         struct node *next;
 14     };
 15     long n,m,dis[10001],add[10001],mindis,minadd;
 16     long total=0,i,j,a,b,c,d,dist;
 17     bool vis[10001]={false};
 18     struct node *info[10001],*p;
 19 
 20     scanf("%ld%ld",&n,&m);
 21     for (i=1;i<=n;i++)
 22         info[i]=NULL;
 23 
 24     for (i=1;i<=m;i++)
 25     {
 26         scanf("%ld%ld%ld",&a,&b,&c);
 27         p=(struct node *) malloc (sizeof(struct node));
 28         p->point=b;
 29         p->len=c;
 30         if (info[a]!=NULL)
 31         {
 32             p->next=info[a];
 33             info[a]=p;
 34         }
 35         else
 36         {
 37             p->next=NULL;
 38             info[a]=p;
 39         }
 40         p=(struct node *) malloc (sizeof(struct node));
 41         p->point=a;
 42         p->len=c;
 43         p->next=NULL;
 44         if (info[b]!=NULL)
 45         {
 46             p->next=info[b];
 47             info[b]=p;
 48         }
 49         else
 50         {
 51             p->next=NULL;
 52             info[b]=p;
 53         }
 54     }
 55     for (i=1;i<=n;i++)
 56     {
 57         dis[i]=max_;
 58         add[i]=max_;
 59     }
 60     p=info[1];
 61     while (p)
 62     {
 63         dis[p->point]=p->len;
 64         add[p->point]=p->len;
 65         p=p->next;
 66     }
 67     dis[1]=0;
 68     add[1]=0;
 69     vis[1]=true;
 70     for (i=1;i<n;i++)
 71     {
 72         mindis=max_;
 73         minadd=max_;
 74         for (j=1;j<=n;j++)
 75             if (!vis[j])
 76             {
 77                 if (dis[j]<mindis)
 78                 {
 79                     mindis=dis[j];
 80                     minadd=add[j];
 81                     d=j;
 82                 }
 83                 else if (dis[j]==mindis && add[j]<minadd)
 84                 {
 85                     minadd=add[j];
 86                     d=j;
 87                 }
 88             }
 89         total+=minadd;
 90         vis[d]=true;
 91         //m<100000 , (x,y)对有2*m个,只要判断2*m次即可
 92         p=info[d];
 93         while (p)
 94         {
 95             if (!vis[p->point])
 96             {
 97                 dist=mindis+p->len;
 98                 if (dist<dis[p->point])
 99                 {
100                     dis[p->point]=dist;
101                     add[p->point]=p->len;
102                 }
103                 else if (dist==dis[p->point] && p->len<add[p->point])
104                     add[p->point]=p->len;
105             }
106             p=p->next;
107         }
108     }
109     printf("%ld\n",total);
110     return 0;
111 }
112 /*
113 input:
114 5 7
115 5 1 2
116 5 4 10
117 1 4 7
118 1 2 3
119 2 4 6
120 3 2 4
121 4 3 5
122 output:
123 16
124 */

 注意不能直接开[10001][100001]的数组,会直接空间溢出,也不能开[10000][10000]的点,n=10000时不对。

拓展:

1 ≤ n ≤ 10000,1 ≤ m ≤ 100000

上述程序方法每次找最小距离的点(n个),共找n次;(x,y)对有2*m个,只要判断2*m次即可,2*m<=n*n,时间复杂度为O(n^2)。实际上n*n=100000000理应通不过,但实际上过了。

如果用堆排序,判断2*m次,则最多修改2*m次,每次修改时间复杂度为ln(k)(k为树中点的个数,k<=n),最多要操作ln(10000)*2*100000=1842068次,而不修改的堆排序最坏时间为nlnn=1151292,判断2*m次需要操作200000次,其实算的都是大概的数字,最终的操作次数不会大于算的数字*10,所以用这个方法不会超时。

 

另外我的一个同学想了一个方法,当求得一个点到首都1距离最短时,就把最短距离要经过的边都设为0,然后继续求其他点到首都的最短距离。

这方法不对,不能保证每个点到首都都是最短距离,其实他求的是最小生成树。假如有3个点,点1到点2的有一条长度为点5的道路,点2到点3有一条长度为3的道路,点1到点3有一条长度为6的道路,那么我们应选择点1到点3的路径来保证3到首都的距离是最短的,而不是选择点2到点3的道路。

求最小生成树(所有点都能通过边到达,边的长度之和最小)的时候,不保证两点之间的距离就是最短路径;同样的,求其他点到一个点的最短路径时,不保证这些路径之和就是最小生成树。但是这两者选取的边的数目都是n-1条(n为点的数目),如果最小生成树的边多于n-1条,则必形成环,删去环里的一条边仍能满足所有点都能通过边到达的条件;而求最短路径,用dijstra的方法,求第k个点时需要S集合中的一个点通过一条边与第k个点相连,每多一个点,最短路径就多一条边。

posted @ 2017-03-28 10:48  congmingyige  阅读(267)  评论(0编辑  收藏  举报