Time limit 2500 ms
Memory limit 262144 kBnode
集训了两个星期,感受本身水平也在不断提升,挺好。前天补了四五题hdu多校,昨天补了五六道CF题,今天来更新博客。app
话说回来,补比赛题加写博客,致使集训内容落下了很多,树剖专题只作了一题,主席树和线段树进阶还各差一题,线性基还没开始学,积性函数又已经开始了……ide
这是一场div3的最后一题。函数
给一张\(n\)个点\(m\)条边的无向连通图,让求这张图上全部最短路中,第\(k\)短的最短路(交换起点终点算同一条路)。ui
地址this
The main observation is that you don't need more than smallest by weight edges (among all edges with the maximum weights you can choose any). Maybe there will be a proof later, but now I ask other participant to write it.spa
So you sort the initial edges and after that you can construct a graph consisting of no more than vertices and no more than edges. You just can build the new graph consisting only on these vertices and edges and run Floyd-Warshall algorithm to find the matrix of shortest paths. Then sort all shorted distances and print the -th element of this sorted array..net
Time complexity: .code
I know that there are other approaches that can solve this problem with greater , but to make this problem easily this solution is enough.xml
I'll first present the solution I used in contest. Then, I'll discuss a simpler solution, courtesy of dorijanlendvaj.
Recall that Dijkstra's algorithm visits points in increasing order of their distance from the source node. Therefore, if we wanted to find the K'th shortest path from a single node, we could do so more quickly by running Dijkstra's and terminating as soon as we've visited K other nodes. We exploit a similar property to solve the problem while considering paths from all nodes.
First, we read in the data. For each vertex, we sort the edges coming from that vertex in increasing order of weight. (Ties can be broken arbitrarily.) Then, we define a "state" variable. A state consists of a shortest path from a starting vertex to some other vertex in which we log four variables: the starting node, the second-to-last node on the path, the index of the final edge in the second-to-last node's adjacency list, and the length of the path.
We maintain a priority queue of these states, pulling the least-weight ones first. Start by adding a state for the single-edge paths using each node's shortest edge. Then, to process a state, we first check that the shortest path from our starting node to the ending node has not been found yet. If not, we add this length to our list and output it if it's the K'th one we've found. Then, we add a new state to the priority queue representing the path from our starting node to the ending node, plus the shortest edge of the ending node.
Then, we add a new state in which we replace the last edge of the current state with the next-shortest edge from our second-to-last node. In other words, we increase the index variable in our state by one.
The key reason this method works is that it guarantees that we process all possible states in increasing order of weight. Moreover, we start with NN states, and each state only creates a second extra state if it creates one of our \(K\) paths. Therefore, we'll end up with \(O(N+K)\)states in total.
Due to the priority queue operations and sorting, our runtime is \(O((N+K) \log (N+K) + M \log M)\).
Here's a second solution. I haven't submitted this one myself, so please feel free to comment if you spot any errors. (Although I received this solution from Dorijan, I wrote it up myself, so all blame for any errors should go to me.)
Eliminate all edges except the \(K\) cheapest ones, with ties broken arbitrarily. Then, run \(N\) Dijkstra's iterations or an efficient all-pairs shortest paths algorithm to find all possible lengths of paths. From here, sort the paths and pick the K'th least expensive one. This is correct because none of the paths we're looking for will include an edge other than one of the \(K\) cheapest ones. Moreover, it's fairly easy to see that this will run in time: there will be, at most, \(O(K^2)\) paths resulting from this process.
上面的题解都没太看懂……看别人的博客一下就看懂的。
我来尝试证实一下……
选前k条最短路,无非两种状况——
对于第二种状况,因为边权都是正数,因此组成第k条路径的那些子路径长度确定都要更小,即它们都是前k短的边之一。
又由于题目里\(k\)比较小,才400,也就是最多800个点,因而咱们取出前k短那些的路径,直接跑Floyd(998ms),将跑出来的结果整理、排序,输出第k大就好。
这里还有一个问题。举个例子,若是\(k==5\),前6条边长度为\(\{1,2,2,3,3,3,4\}\),发现边长第k短长度为3,而边长为3的边有不少,所有选进来就超过k了,那么哪些3应该被选进来呢……想想发现,这依然能够保证正确性。能够分状况讨论一下——
#include<cstdio> #include<cstring> #include<algorithm> int n,m,k; struct Edge{ int u,v; long long w; bool operator <(const Edge & a)const{ return w<a.w; } }e[200010]; int val[200010]; int cnt=1; long long mp[805][805];//开405不够,由于可能选到的400条边都没有公共点,那样就有800个点了(RE on test12) long long ans [160010],num=1; int main() { scanf("%d%d%d",&n,&m,&k); for(int i=1;i<=m;i++) scanf("%d%d%I64d",&e[i].u,&e[i].v,&e[i].w); std::sort(e+1,e+m+1); memset(mp,0x2f,sizeof(mp)); for(int i=1;i<=k;i++) { mp[i][i]=0; } for(int i=1;i<=k;i++) { int u=e[i].u,v=e[i].v,w=e[i].w; if(val[u]==0) val[u]=cnt++;//把点的序号离散化一下 if(val[v]==0) val[v]=cnt++; mp[val[u]][val[v]]=w; mp[val[v]][val[u]]=w; } for(int kk=1;kk<cnt;kk++) for(int i=1;i<cnt;i++) for(int j=1;j<cnt;j++) if(mp[i][j]-mp[i][kk]>mp[kk][j]) mp[i][j]=mp[i][kk]+mp[kk][j]; for(int i=1;i<cnt-1;i++) for(int j=i+1;j<cnt;j++) ans[num++]=mp[i][j]; std::sort(ans+1,ans+num); printf("%I64d\n",ans[k]); return 0; }