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个点相连,每多一个点,最短路径就多一条边。