CodeForces 1196F K-th Path
感想
集训了两个星期,感觉自己水平也在不断提高,挺好。前天补了四五题hdu多校,昨天补了五六道CF题,今天来更新博客。
话说回来,补比赛题加写博客,导致集训内容落下了不少,树剖专题只做了一题,主席树和线段树进阶还各差一题,线性基还没开始学,积性函数又已经开始了……
这是一场div3的最后一题。
中文题意
给一张\(n\)个点\(m\)条边的无向连通图,让求这张图上所有最短路中,第\(k\)短的最短路(交换起点终点算同一条路)。
解题思路
官方题解
1196F - K-th Path
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.
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.
Time complexity: .
I know that there are other approaches that can solve this problem with greater , but to make this problem easily this solution is enough.
另一个版本的官方题解
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条最短路都只含有1条边
- 前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应该被选进来呢……想一想发现,这依然可以保证正确性。可以分情况讨论一下——
- 如果答案是一条边组成的,那么那些边权为3的边,选任意组合进来,输出答案都是3
- 如果答案是多条边组成的,那么答案就是一定小于等于3(大于3就排到k以外了),而且其中每一条边长度都小于3。而所有长度小于3的边我们都已经全部选进来了,所以Floyd会给出正确答案的。
源代码
#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;
}