专题·K短路【including Poj2449 Remmarguts' Date,洛谷·[USACO08MAR] 牛慢跑Cow Jogging

初见安~本专题前置专题:最短路

K短路

含义就不说了——某两点之间的第k短路。k短路是没有说每条边只能走一次的限制的。

求法:求最短路时入队计数,终点取出k次了则此时的dis为第k短路。这里的队列是优先队列。

证明也很简单,因为用优先队列优化了,所以第一次到达目的地的距离一定是最短的。同理可得第k次到达终点的距离一定是第k短的。其他点的入队次数不用管,每次无脑扩展队首节点就行了。

当然是有一个优化的——用启发式搜索,写一个估价函数,作为优先队列排序的参考之一。这个问题上,我们就可以直接运用实际数据——当前点到终点的距离。毕竟我们求的是K短路,不时会遇到某条边需要走多次的情况,所以我们为了求K短路,先求一次从终点出发反图的最短路并不是多此一举。

看个例题——bzoj的入门OJ P4771: Poj2449 Remmarguts' Date

Description

给定一张N个点,M条边的有向图,求从起点S到终点T的第K短路的长度,路径允许重复经过点或边。其中 1≤S,T≤
N≤1000,0≤M≤〖10〗^5,1≤K≤1000。注:不熟悉最短路的读者可以简单浏览0x61节,或者假设我们能够在一张
图上求出单源最短路(从一个点出发到其他所有点的最短路径长度),然后在此基础上考虑本题。

Sample Input

2 2
1 2 5
2 1 4
1 2 2

Sample Output

14

Sol

这就是一个求K短路的板子题了。再理一遍思路——j建图时要建一个反图,然后反图最短路跑一遍从终点出发到达各个点的最短路,再写个堆优化的最短路,统计终点出队次数。

有几个细节需要注意:

1.本来终点和起点就不联通的情况【自己想】

2.起点和终点都是一个点的情况,因为一开始放入的就是起点,取出来会判定为终点但是dis为0不合法,所以我们要多取出来一次才行

上代码——

#include<bits/stdc++.h>
#define maxn 1005
#define maxm 100005
using namespace std;
int read()
{
    int x = 0, ch = getchar();
    while(!isdigit(ch)) ch = getchar();
    while(isdigit(ch)) x = (x << 1) + (x << 3) + ch - '0', ch = getchar();
    return x;
}
 
int n, m, s, t, numk;
struct edge
{
    int to, w, nxt;
    edge() {}
    edge(int tt, int ww, int nn) {to = tt, w = ww, nxt = nn;}
}e[maxm], ee[maxm];
 
int head[maxn], k = 0;
int ehead[maxn], ek = 0;
void add(int u, int v, int w)
{
    e[k] = edge(v, w, head[u]);//正图
    head[u] = k++;
    ee[ek] = edge(u, w, ehead[v]);//反图
    ehead[v] = ek++;
}
 
int dis[maxn];
bool vis[maxn];
void spfa(int start)//最短路模板,没什么好说的。就是全程用的是反图
{
    memset(dis, 0x3f, sizeof dis);
    queue<int> q;
    q.push(start), dis[start] = 0; vis[start] = true;
    register int u, v;
    while(q.size())
    {
        u = q.front(); q.pop(); vis[u] = false;
        for(register int i = ehead[u]; ~i; i = ee[i].nxt)
        {
            v = ee[i].to;
            if(dis[u] + ee[i].w < dis[v])
            {
                dis[v] = dis[u] + ee[i].w;
                if(!vis[v]) q.push(v), vis[v] = true;
            }
        }
    }
}
 
struct node
{
    int v, dis1, dis2;//dis1为实际已走距离,dis2为估价距离,其实可以不存直接调用dis[]
    node() {}
    node(int vv, int d1, int d2) {v = vv, dis1 = d1, dis2 = d2;}
    bool operator < (const node &x) const {return dis1 + dis2 > x.dis1 + x.dis2;}
};
 
 
void dij()
{
    priority_queue<node> q;
    int cnt = 0;
    q.push(node(s, 0, 0));
    register int u, v;
    while(q.size())
    {
        node now = q.top(); q.pop();
        u = now.v;
        if(u == t)//终点取出来了
        {
            cnt++;
            if(cnt == numk) {printf("%d\n", now.dis1); return;}//得到k短路了
        }
         
        for(int i = head[u]; ~i; i = e[i].nxt)
        {
            v = e[i].to;
            q.push(node(v, now.dis1 + e[i].w, dis[v]));//直接暴力每个点入队
        }
    }
    puts("-1");//非正常结束,无解
}
 
int main()
{
    memset(head, -1, sizeof head);
    memset(ehead, -1, sizeof ehead);
    n = read(), m = read();
    register int u, v, w;
    for(register int i = 1; i <= m; i++)
        u = read(), v = read(), w = read(), add(u, v, w);
    s = read(), t = read(), numk = read();
     
    spfa(t);//从终点出发求一次最短路,相当于估价函数
    if(dis[s] == 0x3f3f3f3f) {puts("-1"); return 0;}//特判两点不连通
     
    if(s == t) numk++;//特判二,起点终点为同一个点,要多取出来一次
    dij();//求最短路了
     
    return 0;
}

上面这个思路理解后我们可以直接再看一个板子题:洛谷 P2901 [USACO08MAR]牛慢跑Cow Jogging

题目描述

Bessie has taken heed of the evils of sloth and has decided to get fit by jogging from the barn to the pond several times a week. She doesn't want to work too hard, of course, so she only plans to jog downhill to the pond and then amble back to the barn at her leisure.

Bessie also doesn't want to jog any too far either, so she generally takes the shortest sequence of cow paths to get to the pond. Each of the M (1 <= M <= 10,000) cow paths connects two pastures

conveniently numbered 1..N (1 <= N <= 1000). Even more conveniently, the pastures are numbered such that if X>Y then the cow path from pasture X to pasture Y runs downhill. Pasture N is the barn (at the top of the hill) and pasture 1 is the pond (at the bottom).

Just a week into her regimen, Bessie has begun to tire of always taking the same route to get to the pond. She would like to vary her route by taking different cow paths on different days. Specifically, Bessie would like to take exactly K (1 <= K <= 100) different routes for variety. To avoid too much exertion, she wants these to be the K shortest routes from the barn to the pond. Two routes are considered different if they comprise different sequences of cow paths.

Help Bessie determine how strenuous her workout will be by determining the lengths of each of the K shortest routes on the network of pastures. You will be supplied a list of downhill cow paths from X_i to Y_i along with the cow path's length: (X_i, Y_i, D_i) where (1 <= Y_i < X_i; Y_i < X_i <= N). Cowpath i has length D_i (1 <= D_i <= 1,000,000).

贝西尝到了懒惰的恶果——为了减肥,她不得不决定每周花几次时间在牛棚和池塘之间慢跑。但贝西并不想太累,所以她打算只跑从牛棚到池塘的下坡路,然后再慢慢地从池塘走回牛棚。 

同时,贝西也不想跑得太远,所以她只想沿着通向池塘的最短路径跑步。在牧场里,每条道路连接了两个结点(这些结点的编号为1到N,1≤N≤1000)。另外,如果X>?,说明结点X的地势要高于Y,所以下坡的道路是从X通向Y的,贝西所在牛棚的编号为N(最高点),池塘的编号为1(最低点)。 

而然,一周之后,贝西对单调的路线厌倦了,她希望每天可以跑不同的路线,比如说,最好能有K (1≤K≤100)种不同的选择。为了不至于跑得太累,她希望这K条路径是从牛棚到池塘的最短的K条路径。 

请帮助贝西算算她的运动量,即找出网络里最短的K条路径的长度。假设每条道路用(Xi,Yi,Di)表示,其中1≤Yi<Xi≤N,表示这条道路从Xi出发到Yi,其长度为Di (1≤Di≤1,000,000)。

输入格式:

* Line 1: Three space-separated integers: N, M, and K

* Lines 2..M+1: Line i+1 describes a downhill cow path using three space-separated integers: X_i, Y_i, and D_i

输出格式:

* Lines 1..K: Line i contains the length of the i-th shortest route or -1 if no such route exists. If a shortest route length occurs multiple times, be sure to list it multiple times in the output.

输入样例#1: 

5 8 7 
5 4 1 
5 3 1 
5 2 1 
5 1 1 
4 3 4 
3 1 1 
3 2 1 
2 1 1 

输出样例#1: 

1 
2 
2 
3 
6 
7 
-1 

题解:

这个题……很明显,在我们刚刚的代码上原地修改一点点就可以AC了——输出的时候每次计数都输出dis1,如果非正常结束记得for输出-1……

#include<bits/stdc++.h>
#define maxn 1005
#define maxm 100005
using namespace std;
int read()
{
    int x = 0, ch = getchar();
    while(!isdigit(ch)) ch = getchar();
    while(isdigit(ch)) x = (x << 1) + (x << 3) + ch - '0', ch = getchar();
    return x;
}

int n, m, s, t, numk;
struct edge
{
    int to, w, nxt;
    edge() {}
    edge(int tt, int ww, int nn) {to = tt, w = ww, nxt = nn;}
}e[maxm], ee[maxm];

int head[maxn], k = 0;
int ehead[maxn], ek = 0;
void add(int u, int v, int w)
{
    e[k] = edge(v, w, head[u]);
    head[u] = k++;
    ee[ek] = edge(u, w, ehead[v]);
    ehead[v] = ek++;
}

int dis[maxn];
bool vis[maxn];
void spfa(int start)//完全不用动的部分
{
    memset(dis, 0x3f, sizeof dis);
    queue<int> q;
    q.push(start), dis[start] = 0; vis[start] = true;
    register int u, v;
    while(q.size())
    {
        u = q.front(); q.pop(); vis[u] = false;
        for(register int i = ehead[u]; ~i; i = ee[i].nxt)
        {
            v = ee[i].to;
            if(dis[u] + ee[i].w < dis[v])
            {
                dis[v] = dis[u] + ee[i].w;
                if(!vis[v]) q.push(v), vis[v] = true;
            }
        }
    }
}

struct node
{
    int v, dis1, dis2;
    node() {}
    node(int vv, int d1, int d2) {v = vv, dis1 = d1, dis2 = d2;}
    bool operator < (const node &x) const {return dis1 + dis2 > x.dis1 + x.dis2;}
};


void dij()
{
    priority_queue<node> q;
    int cnt = 0;
    q.push(node(n, 0, 0));
    register int u, v;
    while(q.size())
    {
        node now = q.top(); q.pop();
        u = now.v;
        if(u == 1)
        {
            cnt++;//输出要做一点改动
            printf("%d\n", now.dis1);
            if(cnt == numk) return;
        }
        
        for(int i = head[u]; ~i; i = e[i].nxt)
        {
            v = e[i].to;
            q.push(node(v, now.dis1 + e[i].w, dis[v]));
        }
    }
    for(int i = cnt; i < numk; i++) puts("-1");
}

int main()
{
    memset(head, -1, sizeof head);
    memset(ehead, -1, sizeof ehead);
    n = read(), m = read(), numk = read();
    register int u, v, w;
    for(register int i = 1; i <= m; i++)
        u = read(), v = read(), w = read(), add(u, v, w);
    
    spfa(1);//起点为n,终点为1
    if(dis[n] == 0x3f3f3f3f) {for(int i = 1; i <= numk; i++) puts("-1"); return 0;}//不合法输出也要改动
    
    dij();
    
    return 0;
}

怎么样是不是很简单~~~

迎评:)
——End——

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值