CodeForces 1196F K-th Path

Time limit 2500 ms
Memory limit 262144 kBnode

感想

集訓了兩個星期,感受本身水平也在不斷提升,挺好。前天補了四五題hdu多校,昨天補了五六道CF題,今天來更新博客。app

話說回來,補比賽題加寫博客,致使集訓內容落下了很多,樹剖專題只作了一題,主席樹和線段樹進階還各差一題,線性基還沒開始學,積性函數又已經開始了……ide

這是一場div3的最後一題。函數

中文題意

給一張\(n\)個點\(m\)條邊的無向連通圖,讓求這張圖上全部最短路中,第\(k\)短的最短路(交換起點終點算同一條路)。ui

解題思路

官方題解

地址this

1196F - K-th Path

The main observation is that you don't need more than m i n ( k , m ) 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 2 m i n ( k , m ) vertices and no more than m i n ( m , k ) 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 k -th element of this sorted array..net

Time complexity: O ( m log m + k 3 ) .code

I know that there are other approaches that can solve this problem with greater k , 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)\).

Click here for my submission.

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;
}
相關文章
相關標籤/搜索