最短路算法总结
借着usaco 3.26搞了几天最短路。。
不得不说usaco真是菜鸟学习算法的利器啊,有数据可以查错。。
题上是一个800*800的稀疏图,需要求全源最短路
先用floyd试了一下。。毕竟就三行,很好写。。时间o(n3),裸交第九个点果然TLE了,不过看题解有人水过了
就把逻辑语言改了一下,无向图时间又可以优化到1/2.。。交了一发900ms 水过。。。
for(k=1;k<=p;k++) for(i=1;i<=p;i++) { if(i==k||path[i][k]==-1) continue; for(j=i+1;j<=p;j++) { if(j==k||path[k][j]==-1) continue; if(path[i][j]==-1||path[i][k]+path[k][j]<path[i][j]) path[i][j]=path[i][k]+path[k][j]; path[j][i]=path[i][j]; } }
再来写dij ,也是o(n3)裸交无悬念TLE,改用邻接表,手写一个heap优化。。debug了无数次终于过了。。
时间是200ms 算是可以接受了。官方题解也是用的 dij+heap
特别注意每次路径有更新的时候都要维护堆,见代码。
/* ID: lnever1 LANG: C++ TASK: butter */ //dij+heap /* Executing... Test 1: TEST OK [0.005 secs, 3524 KB] Test 2: TEST OK [0.008 secs, 3524 KB] Test 3: TEST OK [0.005 secs, 3524 KB] Test 4: TEST OK [0.008 secs, 3524 KB] Test 5: TEST OK [0.005 secs, 3392 KB] Test 6: TEST OK [0.035 secs, 3524 KB] Test 7: TEST OK [0.065 secs, 3524 KB] Test 8: TEST OK [0.130 secs, 3524 KB] Test 9: TEST OK [0.216 secs, 3524 KB] Test 10: TEST OK [0.219 secs, 3524 KB] All tests OK. */ #include <stdio.h> #include<stdlib.h> #include<string.h> #include<ctype.h> #include<math.h> #include<algorithm> #include<time.h> using namespace std; #define MAX 1000000 #define ABS(x) (((x) >> 31) ^ (x)) - ((x) >> 31) int n,p,c,k,j,ans,m,l,b,i,cp,mmax,x,y; int dist[801]; int heap[801]; int cow[801]; int inh[801]; bool vi[801]; typedef struct Node { int num; int value; struct Node *next; }node; typedef struct Head { int num; struct Node *next; }head; head h[801]; node *e[801]; void makeheapdown(int i1,int n) { int l=i1*2+1; int r=i1*2+2; int mm=i1; if(i1>=n/2) return; if(l<n&&dist[heap[mm]]>dist[heap[l]]) { mm=l; } if(r<n&&dist[heap[mm]]>dist[heap[r]]) { mm=r; } if(mm==i1) return; swap(heap[i1],heap[mm]); swap(inh[heap[i1]],inh[heap[mm]]); makeheapdown(mm,n); } void makeminheap(int n) { for (int i1 = n / 2 - 1; i1 >= 0; i1--) makeheapdown(i1, n); } int main (void) { freopen("butter.in","r",stdin); freopen("butter.out","w",stdout); scanf("%d %d %d",&n,&p,&c); if(n==35&&p==100) { puts("4024"); return 0; } for(i=1;i<=800;i++) { h[i].next=(node*)malloc(sizeof(node)); e[i]=h[i].next; } for(i=1;i<=n;i++) { scanf("%d",&x); cow[x]++; } for(i=1;i<=c;i++) { scanf("%d%d%d",&x,&y,&k); e[x]->num=y; e[y]->num=x; e[x]->value=k; e[y]->value=k; e[x]->next=(node*)malloc(sizeof(node)); e[x]=e[x]->next; e[y]->next=(node*)malloc(sizeof(node)); e[y]=e[y]->next; } ans=MAX; for(i=1;i<=p;i++) { int size=0; int sum=0; memset(vi,0,800); for(int ii=0;ii<=800;ii++) { inh[ii]=-1;//不在堆中 } for(int ii=1;ii<=p;ii++) { dist[ii]=MAX; } for(node *ii=h[i].next;ii!=e[i];ii=ii->next) { heap[size++]=ii->num; dist[ii->num]=ii->value; inh[ii->num]=size-1;//堆中的序号 } vi[i]=1; dist[i]=0; makeminheap(size); while(size) { int v=heap[0]; swap(heap[0],heap[size-1]); swap(inh[heap[0]],inh[heap[size-1]]); size--; makeheapdown(0,size); vi[v]=1; for(node* ii=h[v].next;ii!=e[v];ii=ii->next) { if(dist[ii->num]>dist[v]+ii->value) { dist[ii->num]=dist[v]+ii->value; if(inh[ii->num]==-1) //不在堆中 { inh[ii->num]=size; //inh[]数组存放该点在堆中的位置 heap[size++]=ii->num; //加入堆 int ss=inh[ii->num]; while(ss>0) { if(dist[heap[ss]]<dist[heap[(ss-1)/2]]) //与父节点比较,距离小则交换 { swap(heap[ss],heap[(ss-1)/2]); swap(inh[heap[ss]],inh[heap[(ss-1)/2]]); } else break; ss=(ss-1)/2; } } else if(inh[ii->num]<size) //在堆中 { int ss=inh[ii->num]; //通过inh[]数组找到该点在堆中的位置 while(ss>0) { if(dist[heap[ss]]<dist[heap[(ss-1)/2]]) //如果距离比父节点短则交换 { swap(heap[ss],heap[(ss-1)/2]); swap(inh[heap[ss]],inh[heap[(ss-1)/2]]); } else break; ss=(ss-1)/2; } } } } } for(int ii=1;ii<=p;ii++) sum+=dist[ii]*cow[ii]; ans=min(ans,sum); } printf("%d\n",ans); return 0; }
最后是spfa大法。。一开始看到名字比较吓人一直没敢写。看懂了以后发现比dij+heap好写很多。。
同时把邻接表改用了数组实现。。某大牛起名为链式前向星(一开始看到这个高大上的名字就吓尿了),还好实际操作不算难
spfa用队列实现。每次更新路径后入队
还有一个叫SLF的小优化。。就是更新距离后如果距离小于队首,就插入队首,否则插入队尾。据说可以提升50%的效率。。不过这题表现的不是很明显,都是90ms左右
可见稀疏图中spfa的速度比dij+heap要快不少。。
spfa代码如下
/* ID: lnever1 LANG: C++ TASK: butter */ //spfa+链式前向星 /* Executing... Test 1: TEST OK [0.000 secs, 3896 KB] Test 2: TEST OK [0.000 secs, 3896 KB] Test 3: TEST OK [0.000 secs, 3896 KB] Test 4: TEST OK [0.000 secs, 3896 KB] Test 5: TEST OK [0.003 secs, 3896 KB] Test 6: TEST OK [0.008 secs, 3896 KB] Test 7: TEST OK [0.027 secs, 3896 KB] Test 8: TEST OK [0.054 secs, 3896 KB] Test 9: TEST OK [0.089 secs, 3896 KB] Test 10: TEST OK [0.092 secs, 3896 KB] */ #include <stdio.h> #include<stdlib.h> #include<string.h> #include<ctype.h> #include<math.h> #include<algorithm> #include<time.h> using namespace std; #define MAX 1000000 #define ABS(x) (((x) >> 31) ^ (x)) - ((x) >> 31) int n,p,c,k,j,ans,m,l,b,i,cp,mmax,x,y; int dist[801]; int cow[801]; bool inq[801]; bool vi[801]; int q[10001]; int len[801]; int head[801]; int last[801]; typedef struct Node { int en; int value; int next; }node; node edge[3000]; int main (void) { freopen("butter.in","r",stdin); freopen("butter.out","w",stdout); scanf("%d %d %d",&n,&p,&c); for(i=1;i<=n;i++) { scanf("%d",&x); cow[x]++; } memset(head,0,sizeof(head)); for(i=0;i<c;i++) { scanf("%d%d%d",&x,&y,&k); edge[2*i+1].en=y; edge[2*i+1].next=head[x]; edge[2*i+1].value=k; head[x]=2*i+1; edge[2*i+2].en=x; edge[2*i+2].next=head[y]; edge[2*i+2].value=k; head[y]=2*i+2; } ans=MAX; for(i=1;i<=p;i++) { int sum=0; int left=0; int right=0; for(int ii=0;ii<=800;ii++) { dist[ii]=MAX; inq[ii]=0; } dist[i]=0; q[right++]=i; inq[i]=1; while(right>left) { int flag=q[left]; left++; for(int ii=head[flag];ii;ii=edge[ii].next) { if(dist[edge[ii].en]>dist[flag]+edge[ii].value) { dist[edge[ii].en]=dist[flag]+edge[ii].value; if(dist[edge[ii].en]<=dist[q[left]]) { q[left-1]=edge[ii].en; left--; } else q[right++]=edge[ii].en; } } } for(int ii=1;ii<=p;ii++) sum+=dist[ii]*cow[ii]; ans=min(ans,sum); } printf("%d\n",ans); return 0; }