BZOJ2763 [JLOI2011]飞行路线(分层最短路,dijkstra的堆优化)

Description

Alice和Bob现在要乘飞机旅行,他们选择了一家相对便宜的航空公司。该航空公司一共在n个城市设有业务,设这些城市分别标记为0到n-1,一共有m种航线,每种航线连接两个城市,并且航线有一定的价格。Alice和Bob现在要从一个城市沿着航线到达另一个城市,途中可以进行转机。航空公司对他们这次旅行也推出优惠,他们可以免费在最多k种航线上搭乘飞机。那么Alice和Bob这次出行最少花费多少?

Input

数据的第一行有三个整数,n,m,k,分别表示城市数,航线数和免费乘坐次数。

第二行有两个整数,s,t,分别表示他们出行的起点城市编号和终点城市编号。(0<=s,t

Output

只有一行,包含一个整数,为最少花费。

Sample Input

5 6 1
0 4
0 1 5
1 2 5
2 3 5
3 4 5
2 3 3
0 2 100

Sample Output

8

HINT

对于30%的数据,2<=n<=50,1<=m<=300,k=0;

对于50%的数据,2<=n<=600,1<=m<=6000,0<=k<=1;

对于100%的数据,2<=n<=10000,1<=m<=50000,0<=k<=10.

思路

分层图最短路的模板题,此题洛谷上卡SPFA,所以用堆优化的dijkstra来做.

题意就是,给出你一张无向图,你有k次机会使得某一条边的花费为0,求最小花费.

我们需要利用类似动态规划的思想来进行转移。

我们定义:

  • dis[i][j] d i s [ i ] [ j ] :从起点sti点,使用了j次优惠机会所使用的最小花费.
  • vis[i][j] v i s [ i ] [ j ] :从起点sti点,使用了j次优惠机会这个状态有没有被标记

我们需要对普通的迪杰斯特拉转移的时候进行一些改变:

  1. 不使用免费机会的时候:

    如果 到点u的花费dis[u][k]+从u到v的边权w<到v的边权dis[v][k]
    正常转移更新:dis[v][k]=dis[u][k]+w
    加入堆q.push(node(v,k,dis[v][k]))
  2. 使用免费机会的时候:

    如果 到点u的时候使用了k次机会的花费dis[u][k]<到v点的时候使用了k+1次机会的边权dis[v][k+1]
    这条边免费更新dis[v][k+1]=dis[u][k]
    加入堆q.push(node(v,k+1,dis[v+1][k]))

剩下的就是迪杰斯特拉的堆优化模板了。

代码

#include <bits/stdc++.h>
using namespace std;
#define mem(a, b) memset(a, b, sizeof(a))
const int N = 1e5 + 10;
const int inf = 0x3f3f3f3f;
int n, m, k;
int first[N], tot;

struct edge
{
    int v, w, next;
} e[N * 2];
void add_edge(int u, int v, int w)
{
    e[tot].v = v, e[tot].w = w;
    e[tot].next = first[u];
    first[u] = tot++;
}
struct node
{
    int id, now, k;
    node() {}
    node(int _id, int _k, int _now)
    {
        id = _id, now = _now, k = _k;
    }
    bool friend operator<(node a, node b)
    {
        return a.now > b.now;
    }
};
/*
dis[i][j]:表示从st到i点用了j次免费机会的最小花费
vis[i][j]:表示从st到i点用了j次免费机会有没有被标记过
*/
int dis[N][12], vis[N][12];
void dijkstra(int st)
{
    for (int i = 1; i <= n; i++)
    {
        for (int j = 0; j <= k; j++)
        {
            dis[i][j] = inf;
            vis[i][j] = 0;
        }
    }
    dis[st][0] = 0;
    priority_queue<node> q;
    q.push(node(st, 0, 0));
    while (!q.empty())
    {
        node u = q.top();
        q.pop();
        if (!vis[u.id][u.k])
        {
            vis[u.id][u.k] = 1;
            for (int i = first[u.id]; ~i; i = e[i].next)
            {
                int v = e[i].v, w = e[i].w;
                if (!vis[v][u.k] && dis[u.id][u.k] + w < dis[v][u.k])
                {
                    dis[v][u.k] = dis[u.id][u.k] + w;
                    q.push(node(v, u.k, dis[v][u.k]));
                }
                if (u.k < k && !vis[v][u.k + 1] && dis[u.id][u.k] < dis[v][u.k + 1])
                {
                    dis[v][u.k + 1] = dis[u.id][u.k];
                    q.push(node(v, u.k + 1, dis[v][u.k + 1]));
                }
            }
        }
    }
}
void init()
{
    mem(first, -1);
    tot = 0;
}
int main()
{
    //freopen("in.txt", "r", stdin);
    int u, v, w, st, ed;
    scanf("%d%d%d%d%d", &n, &m, &k, &st, &ed);
    st++, ed++;
    init();
    for (int i = 1; i <= m; i++)
    {
        scanf("%d%d%d", &u, &v, &w);
        u++, v++;
        add_edge(u, v, w);
        add_edge(v, u, w);
    }
    dijkstra(st);
    int ans = inf;
    for (int i = 0; i <= k; i++)
        ans = min(ans, dis[ed][i]);
    printf("%d\n", ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值